chore(tenants): conteudo de publico/ atualizado
This commit is contained in:
@@ -163,6 +163,9 @@ spec:
|
||||
- { key: "preparando.js", path: "preparando.js" }
|
||||
- { key: "privacidade.html", path: "privacidade.html" }
|
||||
- { key: "termos.html", path: "termos.html" }
|
||||
- { key: "time.css", path: "time.css" }
|
||||
- { key: "time.html", path: "time.html" }
|
||||
- { key: "time.js", path: "time.js" }
|
||||
- configMap:
|
||||
name: frontend-publico-binario
|
||||
items:
|
||||
|
||||
@@ -409,7 +409,7 @@ data:
|
||||
|
||||
<form id="formulario" hidden novalidate>
|
||||
<p class="explica">
|
||||
Preencha os dados do seu time e dos jogadores (só o nome e o número da camisa).
|
||||
Preencha os dados do seu time, dos jogadores (só o nome e o número da camisa) e da comissão técnica (nome e função).
|
||||
A organização confere e aprova antes do time entrar na competição.
|
||||
</p>
|
||||
|
||||
@@ -433,12 +433,23 @@ data:
|
||||
<button type="button" id="maisJogador" class="secundario">+ Jogador</button>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Comissão técnica</legend>
|
||||
<div id="comissao"></div>
|
||||
<datalist id="funcoes-comissao">
|
||||
<option value="Técnico"></option><option value="Auxiliar técnico"></option><option value="Preparador físico"></option>
|
||||
<option value="Preparador de goleiros"></option><option value="Médico"></option><option value="Fisioterapeuta"></option>
|
||||
<option value="Massagista"></option><option value="Roupeiro"></option><option value="Dirigente"></option>
|
||||
</datalist>
|
||||
<button type="button" id="maisComissao" class="secundario">+ Pessoa na comissão</button>
|
||||
</fieldset>
|
||||
|
||||
<p id="erro" class="erro" role="alert" hidden></p>
|
||||
<button type="submit" id="enviar">Enviar time</button>
|
||||
</form>
|
||||
</main>
|
||||
<script src="/publico/desafio.js?v=1"></script>
|
||||
<script src="/publico/convite.js?v=2"></script>
|
||||
<script src="/publico/convite.js?v=3"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -465,6 +476,7 @@ data:
|
||||
|
||||
var TOKEN = /^[A-Za-z0-9_-]{20,64}$/;
|
||||
var MAX_JOGADORES = 60;
|
||||
var MAX_COMISSAO = 15;
|
||||
|
||||
var token = decodeURIComponent((location.pathname.split("/").filter(Boolean).pop() || ""));
|
||||
var base = "/api/public/competicoes/convites/" + encodeURIComponent(token);
|
||||
@@ -473,6 +485,7 @@ data:
|
||||
var situacao = document.getElementById("situacao");
|
||||
var formulario = document.getElementById("formulario");
|
||||
var listaJogadores = document.getElementById("jogadores");
|
||||
var listaComissao = document.getElementById("comissao");
|
||||
var erro = document.getElementById("erro");
|
||||
var enviar = document.getElementById("enviar");
|
||||
var ROTULO_ENVIAR = enviar.textContent;
|
||||
@@ -520,6 +533,39 @@ data:
|
||||
return linha;
|
||||
}
|
||||
|
||||
/** Comissao tecnica (21/10/2026): nome e funcao; a funcao tem sugestoes, mas aceita texto livre. */
|
||||
function linhaDeComissao(funcao) {
|
||||
var linha = document.createElement("div");
|
||||
linha.className = "linha-jogador linha-comissao";
|
||||
|
||||
var nome = document.createElement("input");
|
||||
nome.placeholder = "Nome";
|
||||
nome.maxLength = 120;
|
||||
nome.setAttribute("aria-label", "Nome na comissão técnica");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
|
||||
var papel = document.createElement("input");
|
||||
papel.placeholder = "Função";
|
||||
papel.maxLength = 40;
|
||||
papel.value = funcao || "";
|
||||
papel.setAttribute("list", "funcoes-comissao");
|
||||
papel.setAttribute("aria-label", "Função na comissão técnica");
|
||||
papel.setAttribute("data-campo", "funcao");
|
||||
|
||||
var tirar = document.createElement("button");
|
||||
tirar.type = "button";
|
||||
tirar.className = "secundario";
|
||||
tirar.textContent = "×";
|
||||
tirar.setAttribute("aria-label", "Remover da comissão");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(papel);
|
||||
linha.appendChild(tirar);
|
||||
listaComissao.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
function valor(id) {
|
||||
var v = document.getElementById(id).value.trim();
|
||||
return v === "" ? null : v;
|
||||
@@ -534,14 +580,26 @@ data:
|
||||
var c = linhas[i].querySelector('[data-campo="camisa"]').value;
|
||||
jogadores.push({ nome: nome, numeroCamisa: c === "" ? null : parseInt(c, 10) });
|
||||
}
|
||||
var comissao = [];
|
||||
var faltaFuncao = false;
|
||||
var pessoas = listaComissao.querySelectorAll(".linha-comissao");
|
||||
for (var k = 0; k < pessoas.length; k++) {
|
||||
var n = pessoas[k].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!n) continue;
|
||||
var f = pessoas[k].querySelector('[data-campo="funcao"]').value.trim();
|
||||
if (!f) faltaFuncao = true;
|
||||
comissao.push({ nome: n, funcao: f });
|
||||
}
|
||||
return {
|
||||
faltaFuncao: faltaFuncao,
|
||||
nome: valor("nome"),
|
||||
escola: valor("escola"),
|
||||
cidade: valor("cidade"),
|
||||
contatoNome: valor("contatoNome"),
|
||||
contatoTelefone: valor("contatoTelefone"),
|
||||
contatoEmail: valor("contatoEmail"),
|
||||
jogadores: jogadores
|
||||
jogadores: jogadores,
|
||||
comissao: comissao
|
||||
};
|
||||
}
|
||||
|
||||
@@ -555,6 +613,9 @@ data:
|
||||
var corpo = montarCorpo();
|
||||
if (!corpo.nome) { mostrarErro("Informe o nome do time."); return; }
|
||||
if (corpo.jogadores.length > MAX_JOGADORES) { mostrarErro("No máximo " + MAX_JOGADORES + " jogadores."); return; }
|
||||
if (corpo.faltaFuncao) { mostrarErro("Informe a função de cada pessoa da comissão técnica."); return; }
|
||||
if (corpo.comissao.length > MAX_COMISSAO) { mostrarErro("No máximo " + MAX_COMISSAO + " pessoas na comissão técnica."); return; }
|
||||
delete corpo.faltaFuncao;
|
||||
enviar.disabled = true;
|
||||
enviar.textContent = "Verificando…";
|
||||
enviar.setAttribute("aria-busy", "true");
|
||||
@@ -615,6 +676,7 @@ data:
|
||||
situacao.className = "";
|
||||
formulario.hidden = false;
|
||||
for (var i = 0; i < 5; i++) linhaDeJogador();
|
||||
linhaDeComissao("Técnico");
|
||||
}).catch(function () {
|
||||
recusar("Não foi possível abrir o convite agora. Tente de novo em instantes.");
|
||||
});
|
||||
@@ -623,6 +685,9 @@ data:
|
||||
document.getElementById("maisJogador").addEventListener("click", function () {
|
||||
if (listaJogadores.querySelectorAll(".linha-jogador").length < MAX_JOGADORES) linhaDeJogador().querySelector("input").focus();
|
||||
});
|
||||
document.getElementById("maisComissao").addEventListener("click", function () {
|
||||
if (listaComissao.querySelectorAll(".linha-comissao").length < MAX_COMISSAO) linhaDeComissao("").querySelector("input").focus();
|
||||
});
|
||||
formulario.addEventListener("submit", aoEnviar);
|
||||
iniciar();
|
||||
})();
|
||||
@@ -8242,6 +8307,557 @@ data:
|
||||
</body>
|
||||
</html>
|
||||
|
||||
time.css: |
|
||||
/* Link do time: página pública, sem fonte nem script de fora (a CSP só deixa 'self'). Mobile primeiro. */
|
||||
main { max-width: 56rem; }
|
||||
.explica, .legenda { color: var(--muted); font-size: .9rem; }
|
||||
.abas { display: flex; gap: .25rem; overflow-x: auto; border-bottom: 2px solid var(--line); margin: 0 0 1rem; }
|
||||
.abas button { background: transparent; color: var(--navy); border: 0; border-bottom: 3px solid transparent; border-radius: 0; padding: .6rem .9rem; white-space: nowrap; font: inherit; font-weight: 700; cursor: pointer; }
|
||||
.abas button[aria-selected="true"] { border-bottom-color: var(--orange); }
|
||||
.abas button:focus-visible, button:focus-visible, input:focus-visible { outline: 3px solid var(--orange); outline-offset: 1px; }
|
||||
h2 { font-size: 1.15rem; margin: .5rem 0; }
|
||||
.rolagem { overflow-x: auto; }
|
||||
table { border-collapse: collapse; width: 100%; font-size: .9rem; background: #fff; }
|
||||
th, td { padding: .4rem .45rem; text-align: center; border-bottom: 1px solid var(--line); white-space: nowrap; }
|
||||
th:nth-child(2), td:nth-child(2) { text-align: left; white-space: normal; min-width: 8rem; }
|
||||
tr.meu { background: #fff4e5; font-weight: 700; }
|
||||
.forma { display: inline-flex; gap: 3px; vertical-align: middle; }
|
||||
.ponto { width: .8rem; height: .8rem; border-radius: 50%; display: inline-block; }
|
||||
.ponto.V { background: #067647; }
|
||||
.ponto.E { background: #98a2b3; }
|
||||
.ponto.D { background: #b42318; }
|
||||
.cartao { display: inline-block; width: .7rem; height: .95rem; border-radius: 2px; vertical-align: middle; margin-right: 3px; }
|
||||
.cartao.amarelo { background: #f5c518; }
|
||||
.cartao.vermelho { background: #d92d20; }
|
||||
.jogo, .sumula { background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: .6rem .8rem; margin: 0 0 .6rem; }
|
||||
.jogo .quando { color: var(--muted); font-size: .85rem; }
|
||||
.jogo .confronto { display: grid; grid-template-columns: 1fr auto 1fr; gap: .5rem; align-items: center; font-weight: 700; }
|
||||
.jogo .confronto span:first-child { text-align: right; }
|
||||
.jogo .placar { background: var(--navy); color: #fff; border-radius: 6px; padding: .1rem .6rem; }
|
||||
.etiqueta { display: inline-block; font-size: .75rem; font-weight: 700; padding: .05rem .5rem; border-radius: 99px; background: #fff1e3; border: 1px solid var(--orange); margin-left: .4rem; }
|
||||
.sumula h3 { margin: 0 0 .3rem; font-size: 1rem; }
|
||||
.sumula ul { margin: .2rem 0 .5rem; padding-left: 1.2rem; }
|
||||
form { display: grid; gap: 1rem; }
|
||||
fieldset { border: 1px solid var(--line); border-radius: 8px; background: #fff; padding: .75rem 1rem 1rem; display: grid; gap: .6rem; }
|
||||
legend { font-weight: 700; padding: 0 .4rem; }
|
||||
input { font: inherit; padding: .5rem .6rem; border: 1px solid var(--line); border-radius: 6px; width: 100%; }
|
||||
.linha-ficha { display: grid; grid-template-columns: 1fr 5.5rem auto; gap: .4rem; align-items: center; }
|
||||
.linha-ficha button { padding: .4rem .6rem; }
|
||||
button { font: inherit; font-weight: 700; padding: .65rem 1.1rem; border: 0; border-radius: 8px; background: var(--orange); color: #fff; cursor: pointer; }
|
||||
button.secundario { background: #fff; color: var(--navy); border: 1px solid var(--line); justify-self: start; }
|
||||
button:disabled { opacity: .55; cursor: not-allowed; }
|
||||
.erro { color: #b42318; margin: 0; font-weight: 600; }
|
||||
.ok { background: #e7f6ec; border-left: 4px solid #067647; padding: .8rem 1rem; margin: 0; }
|
||||
#situacao.invalido { background: #fff1e3; border-left: 4px solid var(--orange); padding: 1rem 1.2rem; }
|
||||
.somente-leitura li { margin: .15rem 0; }
|
||||
@media (max-width: 30rem) { .linha-ficha { grid-template-columns: 1fr 4.5rem auto; } }
|
||||
|
||||
time.html: |
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<title>Seu time na competição — Athletic Map</title>
|
||||
<link rel="stylesheet" href="/publico/_estilo.css">
|
||||
<link rel="stylesheet" href="/publico/time.css?v=1">
|
||||
</head>
|
||||
<body>
|
||||
<header><div class="marca">Athletic <span>Map</span></div></header>
|
||||
<main>
|
||||
<h1 id="titulo">Seu time na competição</h1>
|
||||
<p id="situacao" role="status" aria-live="polite">Abrindo o link…</p>
|
||||
|
||||
<div id="painel" hidden>
|
||||
<div class="abas" role="tablist" aria-label="Seções">
|
||||
<button type="button" role="tab" id="aba-tabela" aria-controls="sec-tabela" aria-selected="true" data-aba="tabela">Tabela</button>
|
||||
<button type="button" role="tab" id="aba-jogos" aria-controls="sec-jogos" aria-selected="false" tabindex="-1" data-aba="jogos">Jogos</button>
|
||||
<button type="button" role="tab" id="aba-sumulas" aria-controls="sec-sumulas" aria-selected="false" tabindex="-1" data-aba="sumulas">Súmulas</button>
|
||||
<button type="button" role="tab" id="aba-ficha" aria-controls="sec-ficha" aria-selected="false" tabindex="-1" data-aba="ficha">Ficha do time</button>
|
||||
</div>
|
||||
|
||||
<section id="sec-tabela" role="tabpanel" aria-labelledby="aba-tabela">
|
||||
<h2>Tabela</h2>
|
||||
<div class="rolagem" id="tabela"></div>
|
||||
<p class="legenda">P pontos · J jogos · V vitórias · E empates · D derrotas · GP gols pró · GC gols contra · SG saldo de gols · % aproveitamento · CA cartões amarelos · CV cartões vermelhos</p>
|
||||
</section>
|
||||
|
||||
<section id="sec-jogos" role="tabpanel" aria-labelledby="aba-jogos" hidden>
|
||||
<h2>Jogos</h2>
|
||||
<div id="jogos"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-sumulas" role="tabpanel" aria-labelledby="aba-sumulas" hidden>
|
||||
<h2>Súmulas</h2>
|
||||
<div id="sumulas"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-ficha" role="tabpanel" aria-labelledby="aba-ficha" hidden>
|
||||
<h2>Ficha do time</h2>
|
||||
<p id="ficha-aviso" class="explica"></p>
|
||||
<form id="ficha" novalidate>
|
||||
<fieldset>
|
||||
<legend>Jogadores</legend>
|
||||
<div id="jogadores"></div>
|
||||
<button type="button" id="maisJogador" class="secundario">+ Jogador</button>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Comissão técnica</legend>
|
||||
<div id="comissao"></div>
|
||||
<datalist id="funcoes-comissao">
|
||||
<option value="Técnico"></option><option value="Auxiliar técnico"></option><option value="Preparador físico"></option>
|
||||
<option value="Preparador de goleiros"></option><option value="Médico"></option><option value="Fisioterapeuta"></option>
|
||||
<option value="Massagista"></option><option value="Roupeiro"></option><option value="Dirigente"></option>
|
||||
</datalist>
|
||||
<button type="button" id="maisComissao" class="secundario">+ Pessoa na comissão</button>
|
||||
</fieldset>
|
||||
<p id="ficha-erro" class="erro" role="alert" hidden></p>
|
||||
<p id="ficha-ok" class="ok" role="status" hidden></p>
|
||||
<button type="submit" id="salvar">Salvar ficha</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<script src="/publico/time.js?v=1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
time.js: |
|
||||
/* ===========================================================================
|
||||
* Link do time — a página que UMA pessoa do time abre pelo link com chave.
|
||||
*
|
||||
* Sem login: a chave no endereço é o único segredo. A página mostra a tabela,
|
||||
* os jogos e as súmulas da competição e a ficha do time; se o time for de
|
||||
* fora e ainda não houver jogo encerrado, a ficha pode ser corrigida aqui.
|
||||
*
|
||||
* Tudo o que vem do servidor entra na página com `textContent` — nunca como
|
||||
* HTML. O script é externo porque a CSP é `script-src 'self'`.
|
||||
* =========================================================================== */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var TOKEN = /^[A-Za-z0-9_-]{20,64}$/;
|
||||
var MAX_JOGADORES = 60;
|
||||
var MAX_COMISSAO = 15;
|
||||
var ABAS = ["tabela", "jogos", "sumulas", "ficha"];
|
||||
var ROTULOS_FORMA = { V: "Vitória", E: "Empate", D: "Derrota" };
|
||||
var ROTULOS_STATUS = {
|
||||
AGENDADA: "Agendado", ADIADA: "Adiado", EM_ANDAMENTO: "Em andamento", ENCERRADA: "Encerrado", CANCELADA: "Cancelado"
|
||||
};
|
||||
|
||||
var token = decodeURIComponent(location.pathname.split("/").filter(Boolean).pop() || "");
|
||||
var base = "/api/public/competicoes/times/" + encodeURIComponent(token);
|
||||
|
||||
var titulo = document.getElementById("titulo");
|
||||
var situacao = document.getElementById("situacao");
|
||||
var painel = document.getElementById("painel");
|
||||
var listaJogadores = document.getElementById("jogadores");
|
||||
var listaComissao = document.getElementById("comissao");
|
||||
var formFicha = document.getElementById("ficha");
|
||||
var fichaErro = document.getElementById("ficha-erro");
|
||||
var fichaOk = document.getElementById("ficha-ok");
|
||||
var salvar = document.getElementById("salvar");
|
||||
var visao = null;
|
||||
|
||||
function el(tag, classe, texto) {
|
||||
var e = document.createElement(tag);
|
||||
if (classe) e.className = classe;
|
||||
if (texto !== undefined && texto !== null) e.textContent = String(texto);
|
||||
return e;
|
||||
}
|
||||
|
||||
function limpar(no) {
|
||||
while (no.firstChild) no.removeChild(no.firstChild);
|
||||
}
|
||||
|
||||
function recusar(texto) {
|
||||
situacao.textContent = texto;
|
||||
situacao.className = "invalido";
|
||||
painel.hidden = true;
|
||||
}
|
||||
|
||||
function lerResposta(r) {
|
||||
return r.json().catch(function () { return {}; }).then(function (corpo) { return { status: r.status, corpo: corpo }; });
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ abas
|
||||
|
||||
function abrirAba(nome, focar) {
|
||||
for (var i = 0; i < ABAS.length; i++) {
|
||||
var ativa = ABAS[i] === nome;
|
||||
var aba = document.getElementById("aba-" + ABAS[i]);
|
||||
aba.setAttribute("aria-selected", ativa ? "true" : "false");
|
||||
aba.tabIndex = ativa ? 0 : -1;
|
||||
document.getElementById("sec-" + ABAS[i]).hidden = !ativa;
|
||||
if (ativa && focar) aba.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function aoTeclarNaAba(ev) {
|
||||
var i = ABAS.indexOf(ev.currentTarget.getAttribute("data-aba"));
|
||||
if (ev.key === "ArrowRight") { abrirAba(ABAS[(i + 1) % ABAS.length], true); ev.preventDefault(); }
|
||||
else if (ev.key === "ArrowLeft") { abrirAba(ABAS[(i + ABAS.length - 1) % ABAS.length], true); ev.preventDefault(); }
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ tabela
|
||||
|
||||
function dataBr(iso) {
|
||||
if (!iso) return "Data a definir";
|
||||
var d = new Date(iso);
|
||||
if (isNaN(d.getTime())) return "Data a definir";
|
||||
try {
|
||||
return d.toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" });
|
||||
} catch (e) {
|
||||
return d.toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
function percentual(n) {
|
||||
return (Math.round((n || 0) * 10) / 10).toString().replace(".", ",") + "%";
|
||||
}
|
||||
|
||||
function montarTabela(linhas, meuNome) {
|
||||
var alvo = document.getElementById("tabela");
|
||||
limpar(alvo);
|
||||
if (!linhas || !linhas.length) { alvo.appendChild(el("p", "explica", "A tabela aparece quando houver times e jogos.")); return; }
|
||||
var t = el("table");
|
||||
t.appendChild(el("caption", "explica", "Classificação"));
|
||||
var cab = el("tr");
|
||||
var cols = [["#", "Posição"], ["Time", "Time"], ["P", "Pontos"], ["J", "Jogos"], ["V", "Vitórias"], ["E", "Empates"],
|
||||
["D", "Derrotas"], ["GP", "Gols pró"], ["GC", "Gols contra"], ["SG", "Saldo de gols"], ["%", "Aproveitamento"],
|
||||
["Últimos 5", "Últimos 5 jogos"], ["CA", "Cartões amarelos"], ["CV", "Cartões vermelhos"]];
|
||||
cols.forEach(function (c) {
|
||||
var th = el("th", null, c[0]);
|
||||
th.scope = "col";
|
||||
th.title = c[1];
|
||||
cab.appendChild(th);
|
||||
});
|
||||
var thead = el("thead");
|
||||
thead.appendChild(cab);
|
||||
t.appendChild(thead);
|
||||
var corpo = el("tbody");
|
||||
linhas.forEach(function (l) {
|
||||
var tr = el("tr", l.time === meuNome ? "meu" : "");
|
||||
[l.posicao, l.time, l.pontos, l.jogos, l.vitorias, l.empates, l.derrotas, l.golsPro, l.golsContra, l.saldoGols,
|
||||
percentual(l.aproveitamento)].forEach(function (v) { tr.appendChild(el("td", null, v)); });
|
||||
var forma = el("td");
|
||||
var caixa = el("span", "forma");
|
||||
(l.ultimos || []).forEach(function (r) {
|
||||
var p = el("span", "ponto " + r);
|
||||
p.title = ROTULOS_FORMA[r] || r;
|
||||
p.setAttribute("role", "img");
|
||||
p.setAttribute("aria-label", ROTULOS_FORMA[r] || r);
|
||||
caixa.appendChild(p);
|
||||
});
|
||||
forma.appendChild(caixa);
|
||||
tr.appendChild(forma);
|
||||
var am = el("td");
|
||||
am.appendChild(el("span", "cartao amarelo"));
|
||||
am.appendChild(document.createTextNode(String(l.amarelos || 0)));
|
||||
tr.appendChild(am);
|
||||
var vm = el("td");
|
||||
vm.appendChild(el("span", "cartao vermelho"));
|
||||
vm.appendChild(document.createTextNode(String(l.vermelhos || 0)));
|
||||
tr.appendChild(vm);
|
||||
corpo.appendChild(tr);
|
||||
});
|
||||
t.appendChild(corpo);
|
||||
alvo.appendChild(t);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ jogos
|
||||
|
||||
function montarJogos(jogos) {
|
||||
var alvo = document.getElementById("jogos");
|
||||
limpar(alvo);
|
||||
if (!jogos || !jogos.length) { alvo.appendChild(el("p", "explica", "Ainda não há jogos marcados.")); return; }
|
||||
jogos.forEach(function (j) {
|
||||
var card = el("div", "jogo");
|
||||
var quando = el("div", "quando", (j.rodada ? j.rodada + " · " : "") + dataBr(j.data));
|
||||
var etiqueta = el("span", "etiqueta", ROTULOS_STATUS[j.status] || j.status);
|
||||
quando.appendChild(etiqueta);
|
||||
if (j.wo) quando.appendChild(el("span", "etiqueta", "W.O."));
|
||||
card.appendChild(quando);
|
||||
var linha = el("div", "confronto");
|
||||
linha.appendChild(el("span", null, j.mandante));
|
||||
var temPlacar = j.placarMandante !== undefined && j.placarMandante !== null;
|
||||
linha.appendChild(el("span", temPlacar ? "placar" : "", temPlacar ? j.placarMandante + " x " + j.placarVisitante : "x"));
|
||||
linha.appendChild(el("span", null, j.visitante));
|
||||
card.appendChild(linha);
|
||||
if (j.penaltis) card.appendChild(el("div", "quando", "Pênaltis: " + j.penaltis.mandante + " x " + j.penaltis.visitante));
|
||||
if (j.motivo) card.appendChild(el("div", "quando", "Motivo: " + j.motivo));
|
||||
alvo.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ súmulas
|
||||
|
||||
function pessoa(p) {
|
||||
if (!p) return "jogador não identificado";
|
||||
if (p.nome && p.numeroCamisa !== undefined && p.numeroCamisa !== null) return p.nome + " (" + p.numeroCamisa + ")";
|
||||
if (p.nome) return p.nome;
|
||||
if (p.numeroCamisa !== undefined && p.numeroCamisa !== null) return "camisa " + p.numeroCamisa;
|
||||
return "jogador não identificado";
|
||||
}
|
||||
|
||||
function momento(e) {
|
||||
var m = e.minuto !== undefined && e.minuto !== null ? e.minuto + "'" : "";
|
||||
var t = e.tempo ? e.tempo + "º tempo" : "";
|
||||
return [t, m].filter(Boolean).join(" ") || "—";
|
||||
}
|
||||
|
||||
function secaoDaSumula(card, nome, itens, texto) {
|
||||
if (!itens || !itens.length) return;
|
||||
card.appendChild(el("strong", null, nome));
|
||||
var ul = el("ul");
|
||||
itens.forEach(function (i) { ul.appendChild(el("li", null, texto(i))); });
|
||||
card.appendChild(ul);
|
||||
}
|
||||
|
||||
function montarSumulas(sumulas, jogos) {
|
||||
var alvo = document.getElementById("sumulas");
|
||||
limpar(alvo);
|
||||
if (!sumulas || !sumulas.length) { alvo.appendChild(el("p", "explica", "As súmulas dos jogos do seu time aparecem aqui.")); return; }
|
||||
sumulas.forEach(function (s) {
|
||||
var j = (jogos || []).filter(function (x) { return x.ref === s.jogoRef; })[0];
|
||||
var card = el("div", "sumula");
|
||||
var cab = j ? j.mandante + " x " + j.visitante : "Jogo " + s.jogoRef;
|
||||
if (j && j.placarMandante !== undefined && j.placarMandante !== null) {
|
||||
cab = j.mandante + " " + j.placarMandante + " x " + j.placarVisitante + " " + j.visitante;
|
||||
}
|
||||
card.appendChild(el("h3", null, cab));
|
||||
if (j) card.appendChild(el("div", "quando", (j.rodada ? j.rodada + " · " : "") + dataBr(j.data)));
|
||||
secaoDaSumula(card, "Gols", s.gols, function (g) { return momento(g) + " · " + g.time + " · " + pessoa(g.jogador); });
|
||||
secaoDaSumula(card, "Cartões", s.cartoes, function (c) {
|
||||
return momento(c) + " · " + (c.tipo === "VERMELHO" ? "vermelho" : "amarelo") + " · " + c.time + " · " + pessoa(c.jogador);
|
||||
});
|
||||
secaoDaSumula(card, "Substituições", s.substituicoes, function (t) {
|
||||
return momento(t) + " · " + t.time + " · sai " + pessoa(t.sai) + ", entra " + pessoa(t.entra);
|
||||
});
|
||||
alvo.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ ficha
|
||||
|
||||
function linhaDeJogador(j) {
|
||||
var linha = el("div", "linha-ficha linha-jogador");
|
||||
if (j && j.id) linha.setAttribute("data-id", j.id);
|
||||
var nome = el("input");
|
||||
nome.placeholder = "Nome do jogador";
|
||||
nome.maxLength = 120;
|
||||
nome.value = (j && j.nome) || "";
|
||||
nome.setAttribute("aria-label", "Nome do jogador");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
var camisa = el("input");
|
||||
camisa.type = "number";
|
||||
camisa.min = "0";
|
||||
camisa.max = "999";
|
||||
camisa.placeholder = "nº";
|
||||
camisa.value = j && j.numeroCamisa !== undefined && j.numeroCamisa !== null ? j.numeroCamisa : "";
|
||||
camisa.setAttribute("aria-label", "Número da camisa");
|
||||
camisa.setAttribute("data-campo", "camisa");
|
||||
var tirar = el("button", "secundario", "×");
|
||||
tirar.type = "button";
|
||||
tirar.setAttribute("aria-label", "Remover jogador");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(camisa);
|
||||
linha.appendChild(tirar);
|
||||
listaJogadores.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
function linhaDeComissao(m) {
|
||||
var linha = el("div", "linha-ficha linha-comissao");
|
||||
if (m && m.id) linha.setAttribute("data-id", m.id);
|
||||
var nome = el("input");
|
||||
nome.placeholder = "Nome";
|
||||
nome.maxLength = 120;
|
||||
nome.value = (m && m.nome) || "";
|
||||
nome.setAttribute("aria-label", "Nome na comissão técnica");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
var papel = el("input");
|
||||
papel.placeholder = "Função";
|
||||
papel.maxLength = 40;
|
||||
papel.value = (m && m.funcao) || "";
|
||||
papel.setAttribute("list", "funcoes-comissao");
|
||||
papel.setAttribute("aria-label", "Função na comissão técnica");
|
||||
papel.setAttribute("data-campo", "funcao");
|
||||
var tirar = el("button", "secundario", "×");
|
||||
tirar.type = "button";
|
||||
tirar.setAttribute("aria-label", "Remover da comissão");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(papel);
|
||||
linha.appendChild(tirar);
|
||||
listaComissao.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
/** Time da escola ou ficha fechada: só leitura (listas simples). */
|
||||
function fichaSomenteLeitura(ficha) {
|
||||
formFicha.hidden = true;
|
||||
var alvo = document.getElementById("ficha-leitura");
|
||||
if (!alvo) {
|
||||
alvo = el("div", "somente-leitura");
|
||||
alvo.id = "ficha-leitura";
|
||||
formFicha.parentNode.appendChild(alvo);
|
||||
}
|
||||
limpar(alvo);
|
||||
alvo.appendChild(el("h3", null, "Jogadores"));
|
||||
var ul = el("ul");
|
||||
(ficha.jogadores || []).forEach(function (j) {
|
||||
ul.appendChild(el("li", null, pessoa({ nome: j.nome, numeroCamisa: j.numeroCamisa })));
|
||||
});
|
||||
if (!ul.childNodes.length) ul.appendChild(el("li", null, "Nenhum jogador na ficha."));
|
||||
alvo.appendChild(ul);
|
||||
if ((ficha.comissao || []).length) {
|
||||
alvo.appendChild(el("h3", null, "Comissão técnica"));
|
||||
var uc = el("ul");
|
||||
ficha.comissao.forEach(function (m) { uc.appendChild(el("li", null, m.nome + " — " + m.funcao)); });
|
||||
alvo.appendChild(uc);
|
||||
}
|
||||
}
|
||||
|
||||
function montarFicha(ficha) {
|
||||
var aviso = document.getElementById("ficha-aviso");
|
||||
limpar(listaJogadores);
|
||||
limpar(listaComissao);
|
||||
if (!ficha.editavel) {
|
||||
aviso.textContent = "Esta ficha está fechada para alterações aqui. Se algo estiver errado, fale com a organização.";
|
||||
fichaSomenteLeitura(ficha);
|
||||
return;
|
||||
}
|
||||
aviso.textContent = "Confira os jogadores (nome e número da camisa) e a comissão técnica. Dá para corrigir até o primeiro jogo ser encerrado.";
|
||||
formFicha.hidden = false;
|
||||
(ficha.jogadores || []).forEach(linhaDeJogador);
|
||||
if (!(ficha.jogadores || []).length) for (var i = 0; i < 3; i++) linhaDeJogador();
|
||||
(ficha.comissao || []).forEach(linhaDeComissao);
|
||||
}
|
||||
|
||||
function mostrarErroFicha(texto) {
|
||||
fichaErro.textContent = texto || "";
|
||||
fichaErro.hidden = !texto;
|
||||
}
|
||||
|
||||
function mostrarOkFicha(texto) {
|
||||
fichaOk.textContent = texto || "";
|
||||
fichaOk.hidden = !texto;
|
||||
}
|
||||
|
||||
function corpoDaFicha() {
|
||||
var jogadores = [];
|
||||
var linhas = listaJogadores.querySelectorAll(".linha-jogador");
|
||||
for (var i = 0; i < linhas.length; i++) {
|
||||
var nome = linhas[i].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!nome) continue;
|
||||
var c = linhas[i].querySelector('[data-campo="camisa"]').value;
|
||||
var j = { nome: nome, numeroCamisa: c === "" ? null : parseInt(c, 10) };
|
||||
if (linhas[i].getAttribute("data-id")) j.id = linhas[i].getAttribute("data-id");
|
||||
jogadores.push(j);
|
||||
}
|
||||
var comissao = [];
|
||||
var faltaFuncao = false;
|
||||
var pessoas = listaComissao.querySelectorAll(".linha-comissao");
|
||||
for (var k = 0; k < pessoas.length; k++) {
|
||||
var n = pessoas[k].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!n) continue;
|
||||
var f = pessoas[k].querySelector('[data-campo="funcao"]').value.trim();
|
||||
if (!f) faltaFuncao = true;
|
||||
var m = { nome: n, funcao: f };
|
||||
if (pessoas[k].getAttribute("data-id")) m.id = pessoas[k].getAttribute("data-id");
|
||||
comissao.push(m);
|
||||
}
|
||||
return { jogadores: jogadores, comissao: comissao, faltaFuncao: faltaFuncao };
|
||||
}
|
||||
|
||||
function aoSalvar(ev) {
|
||||
ev.preventDefault();
|
||||
mostrarErroFicha("");
|
||||
mostrarOkFicha("");
|
||||
var c = corpoDaFicha();
|
||||
if (c.jogadores.length > MAX_JOGADORES) { mostrarErroFicha("No máximo " + MAX_JOGADORES + " jogadores."); return; }
|
||||
if (c.faltaFuncao) { mostrarErroFicha("Informe a função de cada pessoa da comissão técnica."); return; }
|
||||
if (c.comissao.length > MAX_COMISSAO) { mostrarErroFicha("No máximo " + MAX_COMISSAO + " pessoas na comissão técnica."); return; }
|
||||
salvar.disabled = true;
|
||||
salvar.setAttribute("aria-busy", "true");
|
||||
fetch(base + "/ficha", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ jogadores: c.jogadores, comissao: c.comissao })
|
||||
}).then(lerResposta).then(function (r) {
|
||||
salvar.disabled = false;
|
||||
salvar.removeAttribute("aria-busy");
|
||||
if (r.status >= 200 && r.status < 300) {
|
||||
mostrarOkFicha("Ficha salva.");
|
||||
return carregar(true);
|
||||
}
|
||||
if (r.status === 404) { recusar("Este link não está mais disponível."); return null; }
|
||||
mostrarErroFicha((r.corpo && r.corpo.detail) || "Não foi possível salvar agora. Tente de novo em instantes.");
|
||||
return null;
|
||||
}).catch(function () {
|
||||
salvar.disabled = false;
|
||||
salvar.removeAttribute("aria-busy");
|
||||
mostrarErroFicha("Sem conexão. Confira a internet e tente de novo.");
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ carga
|
||||
|
||||
function mostrar(v, manterAba) {
|
||||
visao = v;
|
||||
var nomeTime = (v.time && v.time.nome) || "Seu time";
|
||||
titulo.textContent = (v.competicao || "Competição") + " — " + nomeTime;
|
||||
document.title = titulo.textContent + " — Athletic Map";
|
||||
var partes = [];
|
||||
if (v.time && v.time.escola) partes.push(v.time.escola);
|
||||
if (v.time && v.time.cidade) partes.push(v.time.cidade);
|
||||
if (v.temporada && v.temporada.nome) partes.push("Temporada " + v.temporada.nome);
|
||||
if (v.time && v.time.status === "DESQUALIFICADO") partes.push("Time desqualificado");
|
||||
situacao.textContent = partes.join(" · ");
|
||||
situacao.className = "";
|
||||
painel.hidden = false;
|
||||
montarTabela(v.classificacao, nomeTime);
|
||||
montarJogos(v.jogos);
|
||||
montarSumulas(v.sumulas, v.jogos);
|
||||
montarFicha(v.ficha || { jogadores: [], comissao: [], editavel: false });
|
||||
if (!manterAba) abrirAba("tabela", false);
|
||||
}
|
||||
|
||||
function carregar(manterAba) {
|
||||
return fetch(base).then(lerResposta).then(function (r) {
|
||||
if (r.status === 404) { recusar("Este link não está disponível ou foi desativado. Peça um novo à organização."); return; }
|
||||
if (r.status === 429) { recusar("Muitas consultas seguidas. Aguarde alguns minutos e abra o link de novo."); return; }
|
||||
if (r.status !== 200) { recusar("Não foi possível abrir agora. Tente de novo em instantes."); return; }
|
||||
mostrar(r.corpo || {}, manterAba);
|
||||
}).catch(function () {
|
||||
recusar("Não foi possível abrir agora. Tente de novo em instantes.");
|
||||
});
|
||||
}
|
||||
|
||||
function iniciar() {
|
||||
if (!TOKEN.test(token)) { recusar("Este endereço não é válido."); return; }
|
||||
ABAS.forEach(function (nome) {
|
||||
var aba = document.getElementById("aba-" + nome);
|
||||
aba.addEventListener("click", function () { abrirAba(nome, false); });
|
||||
aba.addEventListener("keydown", aoTeclarNaAba);
|
||||
});
|
||||
document.getElementById("maisJogador").addEventListener("click", function () {
|
||||
if (listaJogadores.querySelectorAll(".linha-jogador").length < MAX_JOGADORES) linhaDeJogador().querySelector("input").focus();
|
||||
});
|
||||
document.getElementById("maisComissao").addEventListener("click", function () {
|
||||
if (listaComissao.querySelectorAll(".linha-comissao").length < MAX_COMISSAO) linhaDeComissao(null).querySelector("input").focus();
|
||||
});
|
||||
formFicha.addEventListener("submit", aoSalvar);
|
||||
carregar(false);
|
||||
}
|
||||
|
||||
iniciar();
|
||||
})();
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
|
||||
@@ -153,6 +153,9 @@ spec:
|
||||
- { key: "preparando.js", path: "preparando.js" }
|
||||
- { key: "privacidade.html", path: "privacidade.html" }
|
||||
- { key: "termos.html", path: "termos.html" }
|
||||
- { key: "time.css", path: "time.css" }
|
||||
- { key: "time.html", path: "time.html" }
|
||||
- { key: "time.js", path: "time.js" }
|
||||
- configMap:
|
||||
name: frontend-publico-binario
|
||||
items:
|
||||
|
||||
@@ -409,7 +409,7 @@ data:
|
||||
|
||||
<form id="formulario" hidden novalidate>
|
||||
<p class="explica">
|
||||
Preencha os dados do seu time e dos jogadores (só o nome e o número da camisa).
|
||||
Preencha os dados do seu time, dos jogadores (só o nome e o número da camisa) e da comissão técnica (nome e função).
|
||||
A organização confere e aprova antes do time entrar na competição.
|
||||
</p>
|
||||
|
||||
@@ -433,12 +433,23 @@ data:
|
||||
<button type="button" id="maisJogador" class="secundario">+ Jogador</button>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Comissão técnica</legend>
|
||||
<div id="comissao"></div>
|
||||
<datalist id="funcoes-comissao">
|
||||
<option value="Técnico"></option><option value="Auxiliar técnico"></option><option value="Preparador físico"></option>
|
||||
<option value="Preparador de goleiros"></option><option value="Médico"></option><option value="Fisioterapeuta"></option>
|
||||
<option value="Massagista"></option><option value="Roupeiro"></option><option value="Dirigente"></option>
|
||||
</datalist>
|
||||
<button type="button" id="maisComissao" class="secundario">+ Pessoa na comissão</button>
|
||||
</fieldset>
|
||||
|
||||
<p id="erro" class="erro" role="alert" hidden></p>
|
||||
<button type="submit" id="enviar">Enviar time</button>
|
||||
</form>
|
||||
</main>
|
||||
<script src="/publico/desafio.js?v=1"></script>
|
||||
<script src="/publico/convite.js?v=2"></script>
|
||||
<script src="/publico/convite.js?v=3"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -465,6 +476,7 @@ data:
|
||||
|
||||
var TOKEN = /^[A-Za-z0-9_-]{20,64}$/;
|
||||
var MAX_JOGADORES = 60;
|
||||
var MAX_COMISSAO = 15;
|
||||
|
||||
var token = decodeURIComponent((location.pathname.split("/").filter(Boolean).pop() || ""));
|
||||
var base = "/api/public/competicoes/convites/" + encodeURIComponent(token);
|
||||
@@ -473,6 +485,7 @@ data:
|
||||
var situacao = document.getElementById("situacao");
|
||||
var formulario = document.getElementById("formulario");
|
||||
var listaJogadores = document.getElementById("jogadores");
|
||||
var listaComissao = document.getElementById("comissao");
|
||||
var erro = document.getElementById("erro");
|
||||
var enviar = document.getElementById("enviar");
|
||||
var ROTULO_ENVIAR = enviar.textContent;
|
||||
@@ -520,6 +533,39 @@ data:
|
||||
return linha;
|
||||
}
|
||||
|
||||
/** Comissao tecnica (21/10/2026): nome e funcao; a funcao tem sugestoes, mas aceita texto livre. */
|
||||
function linhaDeComissao(funcao) {
|
||||
var linha = document.createElement("div");
|
||||
linha.className = "linha-jogador linha-comissao";
|
||||
|
||||
var nome = document.createElement("input");
|
||||
nome.placeholder = "Nome";
|
||||
nome.maxLength = 120;
|
||||
nome.setAttribute("aria-label", "Nome na comissão técnica");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
|
||||
var papel = document.createElement("input");
|
||||
papel.placeholder = "Função";
|
||||
papel.maxLength = 40;
|
||||
papel.value = funcao || "";
|
||||
papel.setAttribute("list", "funcoes-comissao");
|
||||
papel.setAttribute("aria-label", "Função na comissão técnica");
|
||||
papel.setAttribute("data-campo", "funcao");
|
||||
|
||||
var tirar = document.createElement("button");
|
||||
tirar.type = "button";
|
||||
tirar.className = "secundario";
|
||||
tirar.textContent = "×";
|
||||
tirar.setAttribute("aria-label", "Remover da comissão");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(papel);
|
||||
linha.appendChild(tirar);
|
||||
listaComissao.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
function valor(id) {
|
||||
var v = document.getElementById(id).value.trim();
|
||||
return v === "" ? null : v;
|
||||
@@ -534,14 +580,26 @@ data:
|
||||
var c = linhas[i].querySelector('[data-campo="camisa"]').value;
|
||||
jogadores.push({ nome: nome, numeroCamisa: c === "" ? null : parseInt(c, 10) });
|
||||
}
|
||||
var comissao = [];
|
||||
var faltaFuncao = false;
|
||||
var pessoas = listaComissao.querySelectorAll(".linha-comissao");
|
||||
for (var k = 0; k < pessoas.length; k++) {
|
||||
var n = pessoas[k].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!n) continue;
|
||||
var f = pessoas[k].querySelector('[data-campo="funcao"]').value.trim();
|
||||
if (!f) faltaFuncao = true;
|
||||
comissao.push({ nome: n, funcao: f });
|
||||
}
|
||||
return {
|
||||
faltaFuncao: faltaFuncao,
|
||||
nome: valor("nome"),
|
||||
escola: valor("escola"),
|
||||
cidade: valor("cidade"),
|
||||
contatoNome: valor("contatoNome"),
|
||||
contatoTelefone: valor("contatoTelefone"),
|
||||
contatoEmail: valor("contatoEmail"),
|
||||
jogadores: jogadores
|
||||
jogadores: jogadores,
|
||||
comissao: comissao
|
||||
};
|
||||
}
|
||||
|
||||
@@ -555,6 +613,9 @@ data:
|
||||
var corpo = montarCorpo();
|
||||
if (!corpo.nome) { mostrarErro("Informe o nome do time."); return; }
|
||||
if (corpo.jogadores.length > MAX_JOGADORES) { mostrarErro("No máximo " + MAX_JOGADORES + " jogadores."); return; }
|
||||
if (corpo.faltaFuncao) { mostrarErro("Informe a função de cada pessoa da comissão técnica."); return; }
|
||||
if (corpo.comissao.length > MAX_COMISSAO) { mostrarErro("No máximo " + MAX_COMISSAO + " pessoas na comissão técnica."); return; }
|
||||
delete corpo.faltaFuncao;
|
||||
enviar.disabled = true;
|
||||
enviar.textContent = "Verificando…";
|
||||
enviar.setAttribute("aria-busy", "true");
|
||||
@@ -615,6 +676,7 @@ data:
|
||||
situacao.className = "";
|
||||
formulario.hidden = false;
|
||||
for (var i = 0; i < 5; i++) linhaDeJogador();
|
||||
linhaDeComissao("Técnico");
|
||||
}).catch(function () {
|
||||
recusar("Não foi possível abrir o convite agora. Tente de novo em instantes.");
|
||||
});
|
||||
@@ -623,6 +685,9 @@ data:
|
||||
document.getElementById("maisJogador").addEventListener("click", function () {
|
||||
if (listaJogadores.querySelectorAll(".linha-jogador").length < MAX_JOGADORES) linhaDeJogador().querySelector("input").focus();
|
||||
});
|
||||
document.getElementById("maisComissao").addEventListener("click", function () {
|
||||
if (listaComissao.querySelectorAll(".linha-comissao").length < MAX_COMISSAO) linhaDeComissao("").querySelector("input").focus();
|
||||
});
|
||||
formulario.addEventListener("submit", aoEnviar);
|
||||
iniciar();
|
||||
})();
|
||||
@@ -8242,6 +8307,557 @@ data:
|
||||
</body>
|
||||
</html>
|
||||
|
||||
time.css: |
|
||||
/* Link do time: página pública, sem fonte nem script de fora (a CSP só deixa 'self'). Mobile primeiro. */
|
||||
main { max-width: 56rem; }
|
||||
.explica, .legenda { color: var(--muted); font-size: .9rem; }
|
||||
.abas { display: flex; gap: .25rem; overflow-x: auto; border-bottom: 2px solid var(--line); margin: 0 0 1rem; }
|
||||
.abas button { background: transparent; color: var(--navy); border: 0; border-bottom: 3px solid transparent; border-radius: 0; padding: .6rem .9rem; white-space: nowrap; font: inherit; font-weight: 700; cursor: pointer; }
|
||||
.abas button[aria-selected="true"] { border-bottom-color: var(--orange); }
|
||||
.abas button:focus-visible, button:focus-visible, input:focus-visible { outline: 3px solid var(--orange); outline-offset: 1px; }
|
||||
h2 { font-size: 1.15rem; margin: .5rem 0; }
|
||||
.rolagem { overflow-x: auto; }
|
||||
table { border-collapse: collapse; width: 100%; font-size: .9rem; background: #fff; }
|
||||
th, td { padding: .4rem .45rem; text-align: center; border-bottom: 1px solid var(--line); white-space: nowrap; }
|
||||
th:nth-child(2), td:nth-child(2) { text-align: left; white-space: normal; min-width: 8rem; }
|
||||
tr.meu { background: #fff4e5; font-weight: 700; }
|
||||
.forma { display: inline-flex; gap: 3px; vertical-align: middle; }
|
||||
.ponto { width: .8rem; height: .8rem; border-radius: 50%; display: inline-block; }
|
||||
.ponto.V { background: #067647; }
|
||||
.ponto.E { background: #98a2b3; }
|
||||
.ponto.D { background: #b42318; }
|
||||
.cartao { display: inline-block; width: .7rem; height: .95rem; border-radius: 2px; vertical-align: middle; margin-right: 3px; }
|
||||
.cartao.amarelo { background: #f5c518; }
|
||||
.cartao.vermelho { background: #d92d20; }
|
||||
.jogo, .sumula { background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: .6rem .8rem; margin: 0 0 .6rem; }
|
||||
.jogo .quando { color: var(--muted); font-size: .85rem; }
|
||||
.jogo .confronto { display: grid; grid-template-columns: 1fr auto 1fr; gap: .5rem; align-items: center; font-weight: 700; }
|
||||
.jogo .confronto span:first-child { text-align: right; }
|
||||
.jogo .placar { background: var(--navy); color: #fff; border-radius: 6px; padding: .1rem .6rem; }
|
||||
.etiqueta { display: inline-block; font-size: .75rem; font-weight: 700; padding: .05rem .5rem; border-radius: 99px; background: #fff1e3; border: 1px solid var(--orange); margin-left: .4rem; }
|
||||
.sumula h3 { margin: 0 0 .3rem; font-size: 1rem; }
|
||||
.sumula ul { margin: .2rem 0 .5rem; padding-left: 1.2rem; }
|
||||
form { display: grid; gap: 1rem; }
|
||||
fieldset { border: 1px solid var(--line); border-radius: 8px; background: #fff; padding: .75rem 1rem 1rem; display: grid; gap: .6rem; }
|
||||
legend { font-weight: 700; padding: 0 .4rem; }
|
||||
input { font: inherit; padding: .5rem .6rem; border: 1px solid var(--line); border-radius: 6px; width: 100%; }
|
||||
.linha-ficha { display: grid; grid-template-columns: 1fr 5.5rem auto; gap: .4rem; align-items: center; }
|
||||
.linha-ficha button { padding: .4rem .6rem; }
|
||||
button { font: inherit; font-weight: 700; padding: .65rem 1.1rem; border: 0; border-radius: 8px; background: var(--orange); color: #fff; cursor: pointer; }
|
||||
button.secundario { background: #fff; color: var(--navy); border: 1px solid var(--line); justify-self: start; }
|
||||
button:disabled { opacity: .55; cursor: not-allowed; }
|
||||
.erro { color: #b42318; margin: 0; font-weight: 600; }
|
||||
.ok { background: #e7f6ec; border-left: 4px solid #067647; padding: .8rem 1rem; margin: 0; }
|
||||
#situacao.invalido { background: #fff1e3; border-left: 4px solid var(--orange); padding: 1rem 1.2rem; }
|
||||
.somente-leitura li { margin: .15rem 0; }
|
||||
@media (max-width: 30rem) { .linha-ficha { grid-template-columns: 1fr 4.5rem auto; } }
|
||||
|
||||
time.html: |
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<title>Seu time na competição — Athletic Map</title>
|
||||
<link rel="stylesheet" href="/publico/_estilo.css">
|
||||
<link rel="stylesheet" href="/publico/time.css?v=1">
|
||||
</head>
|
||||
<body>
|
||||
<header><div class="marca">Athletic <span>Map</span></div></header>
|
||||
<main>
|
||||
<h1 id="titulo">Seu time na competição</h1>
|
||||
<p id="situacao" role="status" aria-live="polite">Abrindo o link…</p>
|
||||
|
||||
<div id="painel" hidden>
|
||||
<div class="abas" role="tablist" aria-label="Seções">
|
||||
<button type="button" role="tab" id="aba-tabela" aria-controls="sec-tabela" aria-selected="true" data-aba="tabela">Tabela</button>
|
||||
<button type="button" role="tab" id="aba-jogos" aria-controls="sec-jogos" aria-selected="false" tabindex="-1" data-aba="jogos">Jogos</button>
|
||||
<button type="button" role="tab" id="aba-sumulas" aria-controls="sec-sumulas" aria-selected="false" tabindex="-1" data-aba="sumulas">Súmulas</button>
|
||||
<button type="button" role="tab" id="aba-ficha" aria-controls="sec-ficha" aria-selected="false" tabindex="-1" data-aba="ficha">Ficha do time</button>
|
||||
</div>
|
||||
|
||||
<section id="sec-tabela" role="tabpanel" aria-labelledby="aba-tabela">
|
||||
<h2>Tabela</h2>
|
||||
<div class="rolagem" id="tabela"></div>
|
||||
<p class="legenda">P pontos · J jogos · V vitórias · E empates · D derrotas · GP gols pró · GC gols contra · SG saldo de gols · % aproveitamento · CA cartões amarelos · CV cartões vermelhos</p>
|
||||
</section>
|
||||
|
||||
<section id="sec-jogos" role="tabpanel" aria-labelledby="aba-jogos" hidden>
|
||||
<h2>Jogos</h2>
|
||||
<div id="jogos"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-sumulas" role="tabpanel" aria-labelledby="aba-sumulas" hidden>
|
||||
<h2>Súmulas</h2>
|
||||
<div id="sumulas"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-ficha" role="tabpanel" aria-labelledby="aba-ficha" hidden>
|
||||
<h2>Ficha do time</h2>
|
||||
<p id="ficha-aviso" class="explica"></p>
|
||||
<form id="ficha" novalidate>
|
||||
<fieldset>
|
||||
<legend>Jogadores</legend>
|
||||
<div id="jogadores"></div>
|
||||
<button type="button" id="maisJogador" class="secundario">+ Jogador</button>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Comissão técnica</legend>
|
||||
<div id="comissao"></div>
|
||||
<datalist id="funcoes-comissao">
|
||||
<option value="Técnico"></option><option value="Auxiliar técnico"></option><option value="Preparador físico"></option>
|
||||
<option value="Preparador de goleiros"></option><option value="Médico"></option><option value="Fisioterapeuta"></option>
|
||||
<option value="Massagista"></option><option value="Roupeiro"></option><option value="Dirigente"></option>
|
||||
</datalist>
|
||||
<button type="button" id="maisComissao" class="secundario">+ Pessoa na comissão</button>
|
||||
</fieldset>
|
||||
<p id="ficha-erro" class="erro" role="alert" hidden></p>
|
||||
<p id="ficha-ok" class="ok" role="status" hidden></p>
|
||||
<button type="submit" id="salvar">Salvar ficha</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<script src="/publico/time.js?v=1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
time.js: |
|
||||
/* ===========================================================================
|
||||
* Link do time — a página que UMA pessoa do time abre pelo link com chave.
|
||||
*
|
||||
* Sem login: a chave no endereço é o único segredo. A página mostra a tabela,
|
||||
* os jogos e as súmulas da competição e a ficha do time; se o time for de
|
||||
* fora e ainda não houver jogo encerrado, a ficha pode ser corrigida aqui.
|
||||
*
|
||||
* Tudo o que vem do servidor entra na página com `textContent` — nunca como
|
||||
* HTML. O script é externo porque a CSP é `script-src 'self'`.
|
||||
* =========================================================================== */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var TOKEN = /^[A-Za-z0-9_-]{20,64}$/;
|
||||
var MAX_JOGADORES = 60;
|
||||
var MAX_COMISSAO = 15;
|
||||
var ABAS = ["tabela", "jogos", "sumulas", "ficha"];
|
||||
var ROTULOS_FORMA = { V: "Vitória", E: "Empate", D: "Derrota" };
|
||||
var ROTULOS_STATUS = {
|
||||
AGENDADA: "Agendado", ADIADA: "Adiado", EM_ANDAMENTO: "Em andamento", ENCERRADA: "Encerrado", CANCELADA: "Cancelado"
|
||||
};
|
||||
|
||||
var token = decodeURIComponent(location.pathname.split("/").filter(Boolean).pop() || "");
|
||||
var base = "/api/public/competicoes/times/" + encodeURIComponent(token);
|
||||
|
||||
var titulo = document.getElementById("titulo");
|
||||
var situacao = document.getElementById("situacao");
|
||||
var painel = document.getElementById("painel");
|
||||
var listaJogadores = document.getElementById("jogadores");
|
||||
var listaComissao = document.getElementById("comissao");
|
||||
var formFicha = document.getElementById("ficha");
|
||||
var fichaErro = document.getElementById("ficha-erro");
|
||||
var fichaOk = document.getElementById("ficha-ok");
|
||||
var salvar = document.getElementById("salvar");
|
||||
var visao = null;
|
||||
|
||||
function el(tag, classe, texto) {
|
||||
var e = document.createElement(tag);
|
||||
if (classe) e.className = classe;
|
||||
if (texto !== undefined && texto !== null) e.textContent = String(texto);
|
||||
return e;
|
||||
}
|
||||
|
||||
function limpar(no) {
|
||||
while (no.firstChild) no.removeChild(no.firstChild);
|
||||
}
|
||||
|
||||
function recusar(texto) {
|
||||
situacao.textContent = texto;
|
||||
situacao.className = "invalido";
|
||||
painel.hidden = true;
|
||||
}
|
||||
|
||||
function lerResposta(r) {
|
||||
return r.json().catch(function () { return {}; }).then(function (corpo) { return { status: r.status, corpo: corpo }; });
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ abas
|
||||
|
||||
function abrirAba(nome, focar) {
|
||||
for (var i = 0; i < ABAS.length; i++) {
|
||||
var ativa = ABAS[i] === nome;
|
||||
var aba = document.getElementById("aba-" + ABAS[i]);
|
||||
aba.setAttribute("aria-selected", ativa ? "true" : "false");
|
||||
aba.tabIndex = ativa ? 0 : -1;
|
||||
document.getElementById("sec-" + ABAS[i]).hidden = !ativa;
|
||||
if (ativa && focar) aba.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function aoTeclarNaAba(ev) {
|
||||
var i = ABAS.indexOf(ev.currentTarget.getAttribute("data-aba"));
|
||||
if (ev.key === "ArrowRight") { abrirAba(ABAS[(i + 1) % ABAS.length], true); ev.preventDefault(); }
|
||||
else if (ev.key === "ArrowLeft") { abrirAba(ABAS[(i + ABAS.length - 1) % ABAS.length], true); ev.preventDefault(); }
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ tabela
|
||||
|
||||
function dataBr(iso) {
|
||||
if (!iso) return "Data a definir";
|
||||
var d = new Date(iso);
|
||||
if (isNaN(d.getTime())) return "Data a definir";
|
||||
try {
|
||||
return d.toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" });
|
||||
} catch (e) {
|
||||
return d.toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
function percentual(n) {
|
||||
return (Math.round((n || 0) * 10) / 10).toString().replace(".", ",") + "%";
|
||||
}
|
||||
|
||||
function montarTabela(linhas, meuNome) {
|
||||
var alvo = document.getElementById("tabela");
|
||||
limpar(alvo);
|
||||
if (!linhas || !linhas.length) { alvo.appendChild(el("p", "explica", "A tabela aparece quando houver times e jogos.")); return; }
|
||||
var t = el("table");
|
||||
t.appendChild(el("caption", "explica", "Classificação"));
|
||||
var cab = el("tr");
|
||||
var cols = [["#", "Posição"], ["Time", "Time"], ["P", "Pontos"], ["J", "Jogos"], ["V", "Vitórias"], ["E", "Empates"],
|
||||
["D", "Derrotas"], ["GP", "Gols pró"], ["GC", "Gols contra"], ["SG", "Saldo de gols"], ["%", "Aproveitamento"],
|
||||
["Últimos 5", "Últimos 5 jogos"], ["CA", "Cartões amarelos"], ["CV", "Cartões vermelhos"]];
|
||||
cols.forEach(function (c) {
|
||||
var th = el("th", null, c[0]);
|
||||
th.scope = "col";
|
||||
th.title = c[1];
|
||||
cab.appendChild(th);
|
||||
});
|
||||
var thead = el("thead");
|
||||
thead.appendChild(cab);
|
||||
t.appendChild(thead);
|
||||
var corpo = el("tbody");
|
||||
linhas.forEach(function (l) {
|
||||
var tr = el("tr", l.time === meuNome ? "meu" : "");
|
||||
[l.posicao, l.time, l.pontos, l.jogos, l.vitorias, l.empates, l.derrotas, l.golsPro, l.golsContra, l.saldoGols,
|
||||
percentual(l.aproveitamento)].forEach(function (v) { tr.appendChild(el("td", null, v)); });
|
||||
var forma = el("td");
|
||||
var caixa = el("span", "forma");
|
||||
(l.ultimos || []).forEach(function (r) {
|
||||
var p = el("span", "ponto " + r);
|
||||
p.title = ROTULOS_FORMA[r] || r;
|
||||
p.setAttribute("role", "img");
|
||||
p.setAttribute("aria-label", ROTULOS_FORMA[r] || r);
|
||||
caixa.appendChild(p);
|
||||
});
|
||||
forma.appendChild(caixa);
|
||||
tr.appendChild(forma);
|
||||
var am = el("td");
|
||||
am.appendChild(el("span", "cartao amarelo"));
|
||||
am.appendChild(document.createTextNode(String(l.amarelos || 0)));
|
||||
tr.appendChild(am);
|
||||
var vm = el("td");
|
||||
vm.appendChild(el("span", "cartao vermelho"));
|
||||
vm.appendChild(document.createTextNode(String(l.vermelhos || 0)));
|
||||
tr.appendChild(vm);
|
||||
corpo.appendChild(tr);
|
||||
});
|
||||
t.appendChild(corpo);
|
||||
alvo.appendChild(t);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ jogos
|
||||
|
||||
function montarJogos(jogos) {
|
||||
var alvo = document.getElementById("jogos");
|
||||
limpar(alvo);
|
||||
if (!jogos || !jogos.length) { alvo.appendChild(el("p", "explica", "Ainda não há jogos marcados.")); return; }
|
||||
jogos.forEach(function (j) {
|
||||
var card = el("div", "jogo");
|
||||
var quando = el("div", "quando", (j.rodada ? j.rodada + " · " : "") + dataBr(j.data));
|
||||
var etiqueta = el("span", "etiqueta", ROTULOS_STATUS[j.status] || j.status);
|
||||
quando.appendChild(etiqueta);
|
||||
if (j.wo) quando.appendChild(el("span", "etiqueta", "W.O."));
|
||||
card.appendChild(quando);
|
||||
var linha = el("div", "confronto");
|
||||
linha.appendChild(el("span", null, j.mandante));
|
||||
var temPlacar = j.placarMandante !== undefined && j.placarMandante !== null;
|
||||
linha.appendChild(el("span", temPlacar ? "placar" : "", temPlacar ? j.placarMandante + " x " + j.placarVisitante : "x"));
|
||||
linha.appendChild(el("span", null, j.visitante));
|
||||
card.appendChild(linha);
|
||||
if (j.penaltis) card.appendChild(el("div", "quando", "Pênaltis: " + j.penaltis.mandante + " x " + j.penaltis.visitante));
|
||||
if (j.motivo) card.appendChild(el("div", "quando", "Motivo: " + j.motivo));
|
||||
alvo.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ súmulas
|
||||
|
||||
function pessoa(p) {
|
||||
if (!p) return "jogador não identificado";
|
||||
if (p.nome && p.numeroCamisa !== undefined && p.numeroCamisa !== null) return p.nome + " (" + p.numeroCamisa + ")";
|
||||
if (p.nome) return p.nome;
|
||||
if (p.numeroCamisa !== undefined && p.numeroCamisa !== null) return "camisa " + p.numeroCamisa;
|
||||
return "jogador não identificado";
|
||||
}
|
||||
|
||||
function momento(e) {
|
||||
var m = e.minuto !== undefined && e.minuto !== null ? e.minuto + "'" : "";
|
||||
var t = e.tempo ? e.tempo + "º tempo" : "";
|
||||
return [t, m].filter(Boolean).join(" ") || "—";
|
||||
}
|
||||
|
||||
function secaoDaSumula(card, nome, itens, texto) {
|
||||
if (!itens || !itens.length) return;
|
||||
card.appendChild(el("strong", null, nome));
|
||||
var ul = el("ul");
|
||||
itens.forEach(function (i) { ul.appendChild(el("li", null, texto(i))); });
|
||||
card.appendChild(ul);
|
||||
}
|
||||
|
||||
function montarSumulas(sumulas, jogos) {
|
||||
var alvo = document.getElementById("sumulas");
|
||||
limpar(alvo);
|
||||
if (!sumulas || !sumulas.length) { alvo.appendChild(el("p", "explica", "As súmulas dos jogos do seu time aparecem aqui.")); return; }
|
||||
sumulas.forEach(function (s) {
|
||||
var j = (jogos || []).filter(function (x) { return x.ref === s.jogoRef; })[0];
|
||||
var card = el("div", "sumula");
|
||||
var cab = j ? j.mandante + " x " + j.visitante : "Jogo " + s.jogoRef;
|
||||
if (j && j.placarMandante !== undefined && j.placarMandante !== null) {
|
||||
cab = j.mandante + " " + j.placarMandante + " x " + j.placarVisitante + " " + j.visitante;
|
||||
}
|
||||
card.appendChild(el("h3", null, cab));
|
||||
if (j) card.appendChild(el("div", "quando", (j.rodada ? j.rodada + " · " : "") + dataBr(j.data)));
|
||||
secaoDaSumula(card, "Gols", s.gols, function (g) { return momento(g) + " · " + g.time + " · " + pessoa(g.jogador); });
|
||||
secaoDaSumula(card, "Cartões", s.cartoes, function (c) {
|
||||
return momento(c) + " · " + (c.tipo === "VERMELHO" ? "vermelho" : "amarelo") + " · " + c.time + " · " + pessoa(c.jogador);
|
||||
});
|
||||
secaoDaSumula(card, "Substituições", s.substituicoes, function (t) {
|
||||
return momento(t) + " · " + t.time + " · sai " + pessoa(t.sai) + ", entra " + pessoa(t.entra);
|
||||
});
|
||||
alvo.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ ficha
|
||||
|
||||
function linhaDeJogador(j) {
|
||||
var linha = el("div", "linha-ficha linha-jogador");
|
||||
if (j && j.id) linha.setAttribute("data-id", j.id);
|
||||
var nome = el("input");
|
||||
nome.placeholder = "Nome do jogador";
|
||||
nome.maxLength = 120;
|
||||
nome.value = (j && j.nome) || "";
|
||||
nome.setAttribute("aria-label", "Nome do jogador");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
var camisa = el("input");
|
||||
camisa.type = "number";
|
||||
camisa.min = "0";
|
||||
camisa.max = "999";
|
||||
camisa.placeholder = "nº";
|
||||
camisa.value = j && j.numeroCamisa !== undefined && j.numeroCamisa !== null ? j.numeroCamisa : "";
|
||||
camisa.setAttribute("aria-label", "Número da camisa");
|
||||
camisa.setAttribute("data-campo", "camisa");
|
||||
var tirar = el("button", "secundario", "×");
|
||||
tirar.type = "button";
|
||||
tirar.setAttribute("aria-label", "Remover jogador");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(camisa);
|
||||
linha.appendChild(tirar);
|
||||
listaJogadores.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
function linhaDeComissao(m) {
|
||||
var linha = el("div", "linha-ficha linha-comissao");
|
||||
if (m && m.id) linha.setAttribute("data-id", m.id);
|
||||
var nome = el("input");
|
||||
nome.placeholder = "Nome";
|
||||
nome.maxLength = 120;
|
||||
nome.value = (m && m.nome) || "";
|
||||
nome.setAttribute("aria-label", "Nome na comissão técnica");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
var papel = el("input");
|
||||
papel.placeholder = "Função";
|
||||
papel.maxLength = 40;
|
||||
papel.value = (m && m.funcao) || "";
|
||||
papel.setAttribute("list", "funcoes-comissao");
|
||||
papel.setAttribute("aria-label", "Função na comissão técnica");
|
||||
papel.setAttribute("data-campo", "funcao");
|
||||
var tirar = el("button", "secundario", "×");
|
||||
tirar.type = "button";
|
||||
tirar.setAttribute("aria-label", "Remover da comissão");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(papel);
|
||||
linha.appendChild(tirar);
|
||||
listaComissao.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
/** Time da escola ou ficha fechada: só leitura (listas simples). */
|
||||
function fichaSomenteLeitura(ficha) {
|
||||
formFicha.hidden = true;
|
||||
var alvo = document.getElementById("ficha-leitura");
|
||||
if (!alvo) {
|
||||
alvo = el("div", "somente-leitura");
|
||||
alvo.id = "ficha-leitura";
|
||||
formFicha.parentNode.appendChild(alvo);
|
||||
}
|
||||
limpar(alvo);
|
||||
alvo.appendChild(el("h3", null, "Jogadores"));
|
||||
var ul = el("ul");
|
||||
(ficha.jogadores || []).forEach(function (j) {
|
||||
ul.appendChild(el("li", null, pessoa({ nome: j.nome, numeroCamisa: j.numeroCamisa })));
|
||||
});
|
||||
if (!ul.childNodes.length) ul.appendChild(el("li", null, "Nenhum jogador na ficha."));
|
||||
alvo.appendChild(ul);
|
||||
if ((ficha.comissao || []).length) {
|
||||
alvo.appendChild(el("h3", null, "Comissão técnica"));
|
||||
var uc = el("ul");
|
||||
ficha.comissao.forEach(function (m) { uc.appendChild(el("li", null, m.nome + " — " + m.funcao)); });
|
||||
alvo.appendChild(uc);
|
||||
}
|
||||
}
|
||||
|
||||
function montarFicha(ficha) {
|
||||
var aviso = document.getElementById("ficha-aviso");
|
||||
limpar(listaJogadores);
|
||||
limpar(listaComissao);
|
||||
if (!ficha.editavel) {
|
||||
aviso.textContent = "Esta ficha está fechada para alterações aqui. Se algo estiver errado, fale com a organização.";
|
||||
fichaSomenteLeitura(ficha);
|
||||
return;
|
||||
}
|
||||
aviso.textContent = "Confira os jogadores (nome e número da camisa) e a comissão técnica. Dá para corrigir até o primeiro jogo ser encerrado.";
|
||||
formFicha.hidden = false;
|
||||
(ficha.jogadores || []).forEach(linhaDeJogador);
|
||||
if (!(ficha.jogadores || []).length) for (var i = 0; i < 3; i++) linhaDeJogador();
|
||||
(ficha.comissao || []).forEach(linhaDeComissao);
|
||||
}
|
||||
|
||||
function mostrarErroFicha(texto) {
|
||||
fichaErro.textContent = texto || "";
|
||||
fichaErro.hidden = !texto;
|
||||
}
|
||||
|
||||
function mostrarOkFicha(texto) {
|
||||
fichaOk.textContent = texto || "";
|
||||
fichaOk.hidden = !texto;
|
||||
}
|
||||
|
||||
function corpoDaFicha() {
|
||||
var jogadores = [];
|
||||
var linhas = listaJogadores.querySelectorAll(".linha-jogador");
|
||||
for (var i = 0; i < linhas.length; i++) {
|
||||
var nome = linhas[i].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!nome) continue;
|
||||
var c = linhas[i].querySelector('[data-campo="camisa"]').value;
|
||||
var j = { nome: nome, numeroCamisa: c === "" ? null : parseInt(c, 10) };
|
||||
if (linhas[i].getAttribute("data-id")) j.id = linhas[i].getAttribute("data-id");
|
||||
jogadores.push(j);
|
||||
}
|
||||
var comissao = [];
|
||||
var faltaFuncao = false;
|
||||
var pessoas = listaComissao.querySelectorAll(".linha-comissao");
|
||||
for (var k = 0; k < pessoas.length; k++) {
|
||||
var n = pessoas[k].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!n) continue;
|
||||
var f = pessoas[k].querySelector('[data-campo="funcao"]').value.trim();
|
||||
if (!f) faltaFuncao = true;
|
||||
var m = { nome: n, funcao: f };
|
||||
if (pessoas[k].getAttribute("data-id")) m.id = pessoas[k].getAttribute("data-id");
|
||||
comissao.push(m);
|
||||
}
|
||||
return { jogadores: jogadores, comissao: comissao, faltaFuncao: faltaFuncao };
|
||||
}
|
||||
|
||||
function aoSalvar(ev) {
|
||||
ev.preventDefault();
|
||||
mostrarErroFicha("");
|
||||
mostrarOkFicha("");
|
||||
var c = corpoDaFicha();
|
||||
if (c.jogadores.length > MAX_JOGADORES) { mostrarErroFicha("No máximo " + MAX_JOGADORES + " jogadores."); return; }
|
||||
if (c.faltaFuncao) { mostrarErroFicha("Informe a função de cada pessoa da comissão técnica."); return; }
|
||||
if (c.comissao.length > MAX_COMISSAO) { mostrarErroFicha("No máximo " + MAX_COMISSAO + " pessoas na comissão técnica."); return; }
|
||||
salvar.disabled = true;
|
||||
salvar.setAttribute("aria-busy", "true");
|
||||
fetch(base + "/ficha", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ jogadores: c.jogadores, comissao: c.comissao })
|
||||
}).then(lerResposta).then(function (r) {
|
||||
salvar.disabled = false;
|
||||
salvar.removeAttribute("aria-busy");
|
||||
if (r.status >= 200 && r.status < 300) {
|
||||
mostrarOkFicha("Ficha salva.");
|
||||
return carregar(true);
|
||||
}
|
||||
if (r.status === 404) { recusar("Este link não está mais disponível."); return null; }
|
||||
mostrarErroFicha((r.corpo && r.corpo.detail) || "Não foi possível salvar agora. Tente de novo em instantes.");
|
||||
return null;
|
||||
}).catch(function () {
|
||||
salvar.disabled = false;
|
||||
salvar.removeAttribute("aria-busy");
|
||||
mostrarErroFicha("Sem conexão. Confira a internet e tente de novo.");
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ carga
|
||||
|
||||
function mostrar(v, manterAba) {
|
||||
visao = v;
|
||||
var nomeTime = (v.time && v.time.nome) || "Seu time";
|
||||
titulo.textContent = (v.competicao || "Competição") + " — " + nomeTime;
|
||||
document.title = titulo.textContent + " — Athletic Map";
|
||||
var partes = [];
|
||||
if (v.time && v.time.escola) partes.push(v.time.escola);
|
||||
if (v.time && v.time.cidade) partes.push(v.time.cidade);
|
||||
if (v.temporada && v.temporada.nome) partes.push("Temporada " + v.temporada.nome);
|
||||
if (v.time && v.time.status === "DESQUALIFICADO") partes.push("Time desqualificado");
|
||||
situacao.textContent = partes.join(" · ");
|
||||
situacao.className = "";
|
||||
painel.hidden = false;
|
||||
montarTabela(v.classificacao, nomeTime);
|
||||
montarJogos(v.jogos);
|
||||
montarSumulas(v.sumulas, v.jogos);
|
||||
montarFicha(v.ficha || { jogadores: [], comissao: [], editavel: false });
|
||||
if (!manterAba) abrirAba("tabela", false);
|
||||
}
|
||||
|
||||
function carregar(manterAba) {
|
||||
return fetch(base).then(lerResposta).then(function (r) {
|
||||
if (r.status === 404) { recusar("Este link não está disponível ou foi desativado. Peça um novo à organização."); return; }
|
||||
if (r.status === 429) { recusar("Muitas consultas seguidas. Aguarde alguns minutos e abra o link de novo."); return; }
|
||||
if (r.status !== 200) { recusar("Não foi possível abrir agora. Tente de novo em instantes."); return; }
|
||||
mostrar(r.corpo || {}, manterAba);
|
||||
}).catch(function () {
|
||||
recusar("Não foi possível abrir agora. Tente de novo em instantes.");
|
||||
});
|
||||
}
|
||||
|
||||
function iniciar() {
|
||||
if (!TOKEN.test(token)) { recusar("Este endereço não é válido."); return; }
|
||||
ABAS.forEach(function (nome) {
|
||||
var aba = document.getElementById("aba-" + nome);
|
||||
aba.addEventListener("click", function () { abrirAba(nome, false); });
|
||||
aba.addEventListener("keydown", aoTeclarNaAba);
|
||||
});
|
||||
document.getElementById("maisJogador").addEventListener("click", function () {
|
||||
if (listaJogadores.querySelectorAll(".linha-jogador").length < MAX_JOGADORES) linhaDeJogador().querySelector("input").focus();
|
||||
});
|
||||
document.getElementById("maisComissao").addEventListener("click", function () {
|
||||
if (listaComissao.querySelectorAll(".linha-comissao").length < MAX_COMISSAO) linhaDeComissao(null).querySelector("input").focus();
|
||||
});
|
||||
formFicha.addEventListener("submit", aoSalvar);
|
||||
carregar(false);
|
||||
}
|
||||
|
||||
iniciar();
|
||||
})();
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
|
||||
@@ -153,6 +153,9 @@ spec:
|
||||
- { key: "preparando.js", path: "preparando.js" }
|
||||
- { key: "privacidade.html", path: "privacidade.html" }
|
||||
- { key: "termos.html", path: "termos.html" }
|
||||
- { key: "time.css", path: "time.css" }
|
||||
- { key: "time.html", path: "time.html" }
|
||||
- { key: "time.js", path: "time.js" }
|
||||
- configMap:
|
||||
name: frontend-publico-binario
|
||||
items:
|
||||
|
||||
@@ -409,7 +409,7 @@ data:
|
||||
|
||||
<form id="formulario" hidden novalidate>
|
||||
<p class="explica">
|
||||
Preencha os dados do seu time e dos jogadores (só o nome e o número da camisa).
|
||||
Preencha os dados do seu time, dos jogadores (só o nome e o número da camisa) e da comissão técnica (nome e função).
|
||||
A organização confere e aprova antes do time entrar na competição.
|
||||
</p>
|
||||
|
||||
@@ -433,12 +433,23 @@ data:
|
||||
<button type="button" id="maisJogador" class="secundario">+ Jogador</button>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Comissão técnica</legend>
|
||||
<div id="comissao"></div>
|
||||
<datalist id="funcoes-comissao">
|
||||
<option value="Técnico"></option><option value="Auxiliar técnico"></option><option value="Preparador físico"></option>
|
||||
<option value="Preparador de goleiros"></option><option value="Médico"></option><option value="Fisioterapeuta"></option>
|
||||
<option value="Massagista"></option><option value="Roupeiro"></option><option value="Dirigente"></option>
|
||||
</datalist>
|
||||
<button type="button" id="maisComissao" class="secundario">+ Pessoa na comissão</button>
|
||||
</fieldset>
|
||||
|
||||
<p id="erro" class="erro" role="alert" hidden></p>
|
||||
<button type="submit" id="enviar">Enviar time</button>
|
||||
</form>
|
||||
</main>
|
||||
<script src="/publico/desafio.js?v=1"></script>
|
||||
<script src="/publico/convite.js?v=2"></script>
|
||||
<script src="/publico/convite.js?v=3"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -465,6 +476,7 @@ data:
|
||||
|
||||
var TOKEN = /^[A-Za-z0-9_-]{20,64}$/;
|
||||
var MAX_JOGADORES = 60;
|
||||
var MAX_COMISSAO = 15;
|
||||
|
||||
var token = decodeURIComponent((location.pathname.split("/").filter(Boolean).pop() || ""));
|
||||
var base = "/api/public/competicoes/convites/" + encodeURIComponent(token);
|
||||
@@ -473,6 +485,7 @@ data:
|
||||
var situacao = document.getElementById("situacao");
|
||||
var formulario = document.getElementById("formulario");
|
||||
var listaJogadores = document.getElementById("jogadores");
|
||||
var listaComissao = document.getElementById("comissao");
|
||||
var erro = document.getElementById("erro");
|
||||
var enviar = document.getElementById("enviar");
|
||||
var ROTULO_ENVIAR = enviar.textContent;
|
||||
@@ -520,6 +533,39 @@ data:
|
||||
return linha;
|
||||
}
|
||||
|
||||
/** Comissao tecnica (21/10/2026): nome e funcao; a funcao tem sugestoes, mas aceita texto livre. */
|
||||
function linhaDeComissao(funcao) {
|
||||
var linha = document.createElement("div");
|
||||
linha.className = "linha-jogador linha-comissao";
|
||||
|
||||
var nome = document.createElement("input");
|
||||
nome.placeholder = "Nome";
|
||||
nome.maxLength = 120;
|
||||
nome.setAttribute("aria-label", "Nome na comissão técnica");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
|
||||
var papel = document.createElement("input");
|
||||
papel.placeholder = "Função";
|
||||
papel.maxLength = 40;
|
||||
papel.value = funcao || "";
|
||||
papel.setAttribute("list", "funcoes-comissao");
|
||||
papel.setAttribute("aria-label", "Função na comissão técnica");
|
||||
papel.setAttribute("data-campo", "funcao");
|
||||
|
||||
var tirar = document.createElement("button");
|
||||
tirar.type = "button";
|
||||
tirar.className = "secundario";
|
||||
tirar.textContent = "×";
|
||||
tirar.setAttribute("aria-label", "Remover da comissão");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(papel);
|
||||
linha.appendChild(tirar);
|
||||
listaComissao.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
function valor(id) {
|
||||
var v = document.getElementById(id).value.trim();
|
||||
return v === "" ? null : v;
|
||||
@@ -534,14 +580,26 @@ data:
|
||||
var c = linhas[i].querySelector('[data-campo="camisa"]').value;
|
||||
jogadores.push({ nome: nome, numeroCamisa: c === "" ? null : parseInt(c, 10) });
|
||||
}
|
||||
var comissao = [];
|
||||
var faltaFuncao = false;
|
||||
var pessoas = listaComissao.querySelectorAll(".linha-comissao");
|
||||
for (var k = 0; k < pessoas.length; k++) {
|
||||
var n = pessoas[k].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!n) continue;
|
||||
var f = pessoas[k].querySelector('[data-campo="funcao"]').value.trim();
|
||||
if (!f) faltaFuncao = true;
|
||||
comissao.push({ nome: n, funcao: f });
|
||||
}
|
||||
return {
|
||||
faltaFuncao: faltaFuncao,
|
||||
nome: valor("nome"),
|
||||
escola: valor("escola"),
|
||||
cidade: valor("cidade"),
|
||||
contatoNome: valor("contatoNome"),
|
||||
contatoTelefone: valor("contatoTelefone"),
|
||||
contatoEmail: valor("contatoEmail"),
|
||||
jogadores: jogadores
|
||||
jogadores: jogadores,
|
||||
comissao: comissao
|
||||
};
|
||||
}
|
||||
|
||||
@@ -555,6 +613,9 @@ data:
|
||||
var corpo = montarCorpo();
|
||||
if (!corpo.nome) { mostrarErro("Informe o nome do time."); return; }
|
||||
if (corpo.jogadores.length > MAX_JOGADORES) { mostrarErro("No máximo " + MAX_JOGADORES + " jogadores."); return; }
|
||||
if (corpo.faltaFuncao) { mostrarErro("Informe a função de cada pessoa da comissão técnica."); return; }
|
||||
if (corpo.comissao.length > MAX_COMISSAO) { mostrarErro("No máximo " + MAX_COMISSAO + " pessoas na comissão técnica."); return; }
|
||||
delete corpo.faltaFuncao;
|
||||
enviar.disabled = true;
|
||||
enviar.textContent = "Verificando…";
|
||||
enviar.setAttribute("aria-busy", "true");
|
||||
@@ -615,6 +676,7 @@ data:
|
||||
situacao.className = "";
|
||||
formulario.hidden = false;
|
||||
for (var i = 0; i < 5; i++) linhaDeJogador();
|
||||
linhaDeComissao("Técnico");
|
||||
}).catch(function () {
|
||||
recusar("Não foi possível abrir o convite agora. Tente de novo em instantes.");
|
||||
});
|
||||
@@ -623,6 +685,9 @@ data:
|
||||
document.getElementById("maisJogador").addEventListener("click", function () {
|
||||
if (listaJogadores.querySelectorAll(".linha-jogador").length < MAX_JOGADORES) linhaDeJogador().querySelector("input").focus();
|
||||
});
|
||||
document.getElementById("maisComissao").addEventListener("click", function () {
|
||||
if (listaComissao.querySelectorAll(".linha-comissao").length < MAX_COMISSAO) linhaDeComissao("").querySelector("input").focus();
|
||||
});
|
||||
formulario.addEventListener("submit", aoEnviar);
|
||||
iniciar();
|
||||
})();
|
||||
@@ -8242,6 +8307,557 @@ data:
|
||||
</body>
|
||||
</html>
|
||||
|
||||
time.css: |
|
||||
/* Link do time: página pública, sem fonte nem script de fora (a CSP só deixa 'self'). Mobile primeiro. */
|
||||
main { max-width: 56rem; }
|
||||
.explica, .legenda { color: var(--muted); font-size: .9rem; }
|
||||
.abas { display: flex; gap: .25rem; overflow-x: auto; border-bottom: 2px solid var(--line); margin: 0 0 1rem; }
|
||||
.abas button { background: transparent; color: var(--navy); border: 0; border-bottom: 3px solid transparent; border-radius: 0; padding: .6rem .9rem; white-space: nowrap; font: inherit; font-weight: 700; cursor: pointer; }
|
||||
.abas button[aria-selected="true"] { border-bottom-color: var(--orange); }
|
||||
.abas button:focus-visible, button:focus-visible, input:focus-visible { outline: 3px solid var(--orange); outline-offset: 1px; }
|
||||
h2 { font-size: 1.15rem; margin: .5rem 0; }
|
||||
.rolagem { overflow-x: auto; }
|
||||
table { border-collapse: collapse; width: 100%; font-size: .9rem; background: #fff; }
|
||||
th, td { padding: .4rem .45rem; text-align: center; border-bottom: 1px solid var(--line); white-space: nowrap; }
|
||||
th:nth-child(2), td:nth-child(2) { text-align: left; white-space: normal; min-width: 8rem; }
|
||||
tr.meu { background: #fff4e5; font-weight: 700; }
|
||||
.forma { display: inline-flex; gap: 3px; vertical-align: middle; }
|
||||
.ponto { width: .8rem; height: .8rem; border-radius: 50%; display: inline-block; }
|
||||
.ponto.V { background: #067647; }
|
||||
.ponto.E { background: #98a2b3; }
|
||||
.ponto.D { background: #b42318; }
|
||||
.cartao { display: inline-block; width: .7rem; height: .95rem; border-radius: 2px; vertical-align: middle; margin-right: 3px; }
|
||||
.cartao.amarelo { background: #f5c518; }
|
||||
.cartao.vermelho { background: #d92d20; }
|
||||
.jogo, .sumula { background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: .6rem .8rem; margin: 0 0 .6rem; }
|
||||
.jogo .quando { color: var(--muted); font-size: .85rem; }
|
||||
.jogo .confronto { display: grid; grid-template-columns: 1fr auto 1fr; gap: .5rem; align-items: center; font-weight: 700; }
|
||||
.jogo .confronto span:first-child { text-align: right; }
|
||||
.jogo .placar { background: var(--navy); color: #fff; border-radius: 6px; padding: .1rem .6rem; }
|
||||
.etiqueta { display: inline-block; font-size: .75rem; font-weight: 700; padding: .05rem .5rem; border-radius: 99px; background: #fff1e3; border: 1px solid var(--orange); margin-left: .4rem; }
|
||||
.sumula h3 { margin: 0 0 .3rem; font-size: 1rem; }
|
||||
.sumula ul { margin: .2rem 0 .5rem; padding-left: 1.2rem; }
|
||||
form { display: grid; gap: 1rem; }
|
||||
fieldset { border: 1px solid var(--line); border-radius: 8px; background: #fff; padding: .75rem 1rem 1rem; display: grid; gap: .6rem; }
|
||||
legend { font-weight: 700; padding: 0 .4rem; }
|
||||
input { font: inherit; padding: .5rem .6rem; border: 1px solid var(--line); border-radius: 6px; width: 100%; }
|
||||
.linha-ficha { display: grid; grid-template-columns: 1fr 5.5rem auto; gap: .4rem; align-items: center; }
|
||||
.linha-ficha button { padding: .4rem .6rem; }
|
||||
button { font: inherit; font-weight: 700; padding: .65rem 1.1rem; border: 0; border-radius: 8px; background: var(--orange); color: #fff; cursor: pointer; }
|
||||
button.secundario { background: #fff; color: var(--navy); border: 1px solid var(--line); justify-self: start; }
|
||||
button:disabled { opacity: .55; cursor: not-allowed; }
|
||||
.erro { color: #b42318; margin: 0; font-weight: 600; }
|
||||
.ok { background: #e7f6ec; border-left: 4px solid #067647; padding: .8rem 1rem; margin: 0; }
|
||||
#situacao.invalido { background: #fff1e3; border-left: 4px solid var(--orange); padding: 1rem 1.2rem; }
|
||||
.somente-leitura li { margin: .15rem 0; }
|
||||
@media (max-width: 30rem) { .linha-ficha { grid-template-columns: 1fr 4.5rem auto; } }
|
||||
|
||||
time.html: |
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<title>Seu time na competição — Athletic Map</title>
|
||||
<link rel="stylesheet" href="/publico/_estilo.css">
|
||||
<link rel="stylesheet" href="/publico/time.css?v=1">
|
||||
</head>
|
||||
<body>
|
||||
<header><div class="marca">Athletic <span>Map</span></div></header>
|
||||
<main>
|
||||
<h1 id="titulo">Seu time na competição</h1>
|
||||
<p id="situacao" role="status" aria-live="polite">Abrindo o link…</p>
|
||||
|
||||
<div id="painel" hidden>
|
||||
<div class="abas" role="tablist" aria-label="Seções">
|
||||
<button type="button" role="tab" id="aba-tabela" aria-controls="sec-tabela" aria-selected="true" data-aba="tabela">Tabela</button>
|
||||
<button type="button" role="tab" id="aba-jogos" aria-controls="sec-jogos" aria-selected="false" tabindex="-1" data-aba="jogos">Jogos</button>
|
||||
<button type="button" role="tab" id="aba-sumulas" aria-controls="sec-sumulas" aria-selected="false" tabindex="-1" data-aba="sumulas">Súmulas</button>
|
||||
<button type="button" role="tab" id="aba-ficha" aria-controls="sec-ficha" aria-selected="false" tabindex="-1" data-aba="ficha">Ficha do time</button>
|
||||
</div>
|
||||
|
||||
<section id="sec-tabela" role="tabpanel" aria-labelledby="aba-tabela">
|
||||
<h2>Tabela</h2>
|
||||
<div class="rolagem" id="tabela"></div>
|
||||
<p class="legenda">P pontos · J jogos · V vitórias · E empates · D derrotas · GP gols pró · GC gols contra · SG saldo de gols · % aproveitamento · CA cartões amarelos · CV cartões vermelhos</p>
|
||||
</section>
|
||||
|
||||
<section id="sec-jogos" role="tabpanel" aria-labelledby="aba-jogos" hidden>
|
||||
<h2>Jogos</h2>
|
||||
<div id="jogos"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-sumulas" role="tabpanel" aria-labelledby="aba-sumulas" hidden>
|
||||
<h2>Súmulas</h2>
|
||||
<div id="sumulas"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-ficha" role="tabpanel" aria-labelledby="aba-ficha" hidden>
|
||||
<h2>Ficha do time</h2>
|
||||
<p id="ficha-aviso" class="explica"></p>
|
||||
<form id="ficha" novalidate>
|
||||
<fieldset>
|
||||
<legend>Jogadores</legend>
|
||||
<div id="jogadores"></div>
|
||||
<button type="button" id="maisJogador" class="secundario">+ Jogador</button>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Comissão técnica</legend>
|
||||
<div id="comissao"></div>
|
||||
<datalist id="funcoes-comissao">
|
||||
<option value="Técnico"></option><option value="Auxiliar técnico"></option><option value="Preparador físico"></option>
|
||||
<option value="Preparador de goleiros"></option><option value="Médico"></option><option value="Fisioterapeuta"></option>
|
||||
<option value="Massagista"></option><option value="Roupeiro"></option><option value="Dirigente"></option>
|
||||
</datalist>
|
||||
<button type="button" id="maisComissao" class="secundario">+ Pessoa na comissão</button>
|
||||
</fieldset>
|
||||
<p id="ficha-erro" class="erro" role="alert" hidden></p>
|
||||
<p id="ficha-ok" class="ok" role="status" hidden></p>
|
||||
<button type="submit" id="salvar">Salvar ficha</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<script src="/publico/time.js?v=1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
time.js: |
|
||||
/* ===========================================================================
|
||||
* Link do time — a página que UMA pessoa do time abre pelo link com chave.
|
||||
*
|
||||
* Sem login: a chave no endereço é o único segredo. A página mostra a tabela,
|
||||
* os jogos e as súmulas da competição e a ficha do time; se o time for de
|
||||
* fora e ainda não houver jogo encerrado, a ficha pode ser corrigida aqui.
|
||||
*
|
||||
* Tudo o que vem do servidor entra na página com `textContent` — nunca como
|
||||
* HTML. O script é externo porque a CSP é `script-src 'self'`.
|
||||
* =========================================================================== */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var TOKEN = /^[A-Za-z0-9_-]{20,64}$/;
|
||||
var MAX_JOGADORES = 60;
|
||||
var MAX_COMISSAO = 15;
|
||||
var ABAS = ["tabela", "jogos", "sumulas", "ficha"];
|
||||
var ROTULOS_FORMA = { V: "Vitória", E: "Empate", D: "Derrota" };
|
||||
var ROTULOS_STATUS = {
|
||||
AGENDADA: "Agendado", ADIADA: "Adiado", EM_ANDAMENTO: "Em andamento", ENCERRADA: "Encerrado", CANCELADA: "Cancelado"
|
||||
};
|
||||
|
||||
var token = decodeURIComponent(location.pathname.split("/").filter(Boolean).pop() || "");
|
||||
var base = "/api/public/competicoes/times/" + encodeURIComponent(token);
|
||||
|
||||
var titulo = document.getElementById("titulo");
|
||||
var situacao = document.getElementById("situacao");
|
||||
var painel = document.getElementById("painel");
|
||||
var listaJogadores = document.getElementById("jogadores");
|
||||
var listaComissao = document.getElementById("comissao");
|
||||
var formFicha = document.getElementById("ficha");
|
||||
var fichaErro = document.getElementById("ficha-erro");
|
||||
var fichaOk = document.getElementById("ficha-ok");
|
||||
var salvar = document.getElementById("salvar");
|
||||
var visao = null;
|
||||
|
||||
function el(tag, classe, texto) {
|
||||
var e = document.createElement(tag);
|
||||
if (classe) e.className = classe;
|
||||
if (texto !== undefined && texto !== null) e.textContent = String(texto);
|
||||
return e;
|
||||
}
|
||||
|
||||
function limpar(no) {
|
||||
while (no.firstChild) no.removeChild(no.firstChild);
|
||||
}
|
||||
|
||||
function recusar(texto) {
|
||||
situacao.textContent = texto;
|
||||
situacao.className = "invalido";
|
||||
painel.hidden = true;
|
||||
}
|
||||
|
||||
function lerResposta(r) {
|
||||
return r.json().catch(function () { return {}; }).then(function (corpo) { return { status: r.status, corpo: corpo }; });
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ abas
|
||||
|
||||
function abrirAba(nome, focar) {
|
||||
for (var i = 0; i < ABAS.length; i++) {
|
||||
var ativa = ABAS[i] === nome;
|
||||
var aba = document.getElementById("aba-" + ABAS[i]);
|
||||
aba.setAttribute("aria-selected", ativa ? "true" : "false");
|
||||
aba.tabIndex = ativa ? 0 : -1;
|
||||
document.getElementById("sec-" + ABAS[i]).hidden = !ativa;
|
||||
if (ativa && focar) aba.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function aoTeclarNaAba(ev) {
|
||||
var i = ABAS.indexOf(ev.currentTarget.getAttribute("data-aba"));
|
||||
if (ev.key === "ArrowRight") { abrirAba(ABAS[(i + 1) % ABAS.length], true); ev.preventDefault(); }
|
||||
else if (ev.key === "ArrowLeft") { abrirAba(ABAS[(i + ABAS.length - 1) % ABAS.length], true); ev.preventDefault(); }
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ tabela
|
||||
|
||||
function dataBr(iso) {
|
||||
if (!iso) return "Data a definir";
|
||||
var d = new Date(iso);
|
||||
if (isNaN(d.getTime())) return "Data a definir";
|
||||
try {
|
||||
return d.toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" });
|
||||
} catch (e) {
|
||||
return d.toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
function percentual(n) {
|
||||
return (Math.round((n || 0) * 10) / 10).toString().replace(".", ",") + "%";
|
||||
}
|
||||
|
||||
function montarTabela(linhas, meuNome) {
|
||||
var alvo = document.getElementById("tabela");
|
||||
limpar(alvo);
|
||||
if (!linhas || !linhas.length) { alvo.appendChild(el("p", "explica", "A tabela aparece quando houver times e jogos.")); return; }
|
||||
var t = el("table");
|
||||
t.appendChild(el("caption", "explica", "Classificação"));
|
||||
var cab = el("tr");
|
||||
var cols = [["#", "Posição"], ["Time", "Time"], ["P", "Pontos"], ["J", "Jogos"], ["V", "Vitórias"], ["E", "Empates"],
|
||||
["D", "Derrotas"], ["GP", "Gols pró"], ["GC", "Gols contra"], ["SG", "Saldo de gols"], ["%", "Aproveitamento"],
|
||||
["Últimos 5", "Últimos 5 jogos"], ["CA", "Cartões amarelos"], ["CV", "Cartões vermelhos"]];
|
||||
cols.forEach(function (c) {
|
||||
var th = el("th", null, c[0]);
|
||||
th.scope = "col";
|
||||
th.title = c[1];
|
||||
cab.appendChild(th);
|
||||
});
|
||||
var thead = el("thead");
|
||||
thead.appendChild(cab);
|
||||
t.appendChild(thead);
|
||||
var corpo = el("tbody");
|
||||
linhas.forEach(function (l) {
|
||||
var tr = el("tr", l.time === meuNome ? "meu" : "");
|
||||
[l.posicao, l.time, l.pontos, l.jogos, l.vitorias, l.empates, l.derrotas, l.golsPro, l.golsContra, l.saldoGols,
|
||||
percentual(l.aproveitamento)].forEach(function (v) { tr.appendChild(el("td", null, v)); });
|
||||
var forma = el("td");
|
||||
var caixa = el("span", "forma");
|
||||
(l.ultimos || []).forEach(function (r) {
|
||||
var p = el("span", "ponto " + r);
|
||||
p.title = ROTULOS_FORMA[r] || r;
|
||||
p.setAttribute("role", "img");
|
||||
p.setAttribute("aria-label", ROTULOS_FORMA[r] || r);
|
||||
caixa.appendChild(p);
|
||||
});
|
||||
forma.appendChild(caixa);
|
||||
tr.appendChild(forma);
|
||||
var am = el("td");
|
||||
am.appendChild(el("span", "cartao amarelo"));
|
||||
am.appendChild(document.createTextNode(String(l.amarelos || 0)));
|
||||
tr.appendChild(am);
|
||||
var vm = el("td");
|
||||
vm.appendChild(el("span", "cartao vermelho"));
|
||||
vm.appendChild(document.createTextNode(String(l.vermelhos || 0)));
|
||||
tr.appendChild(vm);
|
||||
corpo.appendChild(tr);
|
||||
});
|
||||
t.appendChild(corpo);
|
||||
alvo.appendChild(t);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ jogos
|
||||
|
||||
function montarJogos(jogos) {
|
||||
var alvo = document.getElementById("jogos");
|
||||
limpar(alvo);
|
||||
if (!jogos || !jogos.length) { alvo.appendChild(el("p", "explica", "Ainda não há jogos marcados.")); return; }
|
||||
jogos.forEach(function (j) {
|
||||
var card = el("div", "jogo");
|
||||
var quando = el("div", "quando", (j.rodada ? j.rodada + " · " : "") + dataBr(j.data));
|
||||
var etiqueta = el("span", "etiqueta", ROTULOS_STATUS[j.status] || j.status);
|
||||
quando.appendChild(etiqueta);
|
||||
if (j.wo) quando.appendChild(el("span", "etiqueta", "W.O."));
|
||||
card.appendChild(quando);
|
||||
var linha = el("div", "confronto");
|
||||
linha.appendChild(el("span", null, j.mandante));
|
||||
var temPlacar = j.placarMandante !== undefined && j.placarMandante !== null;
|
||||
linha.appendChild(el("span", temPlacar ? "placar" : "", temPlacar ? j.placarMandante + " x " + j.placarVisitante : "x"));
|
||||
linha.appendChild(el("span", null, j.visitante));
|
||||
card.appendChild(linha);
|
||||
if (j.penaltis) card.appendChild(el("div", "quando", "Pênaltis: " + j.penaltis.mandante + " x " + j.penaltis.visitante));
|
||||
if (j.motivo) card.appendChild(el("div", "quando", "Motivo: " + j.motivo));
|
||||
alvo.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ súmulas
|
||||
|
||||
function pessoa(p) {
|
||||
if (!p) return "jogador não identificado";
|
||||
if (p.nome && p.numeroCamisa !== undefined && p.numeroCamisa !== null) return p.nome + " (" + p.numeroCamisa + ")";
|
||||
if (p.nome) return p.nome;
|
||||
if (p.numeroCamisa !== undefined && p.numeroCamisa !== null) return "camisa " + p.numeroCamisa;
|
||||
return "jogador não identificado";
|
||||
}
|
||||
|
||||
function momento(e) {
|
||||
var m = e.minuto !== undefined && e.minuto !== null ? e.minuto + "'" : "";
|
||||
var t = e.tempo ? e.tempo + "º tempo" : "";
|
||||
return [t, m].filter(Boolean).join(" ") || "—";
|
||||
}
|
||||
|
||||
function secaoDaSumula(card, nome, itens, texto) {
|
||||
if (!itens || !itens.length) return;
|
||||
card.appendChild(el("strong", null, nome));
|
||||
var ul = el("ul");
|
||||
itens.forEach(function (i) { ul.appendChild(el("li", null, texto(i))); });
|
||||
card.appendChild(ul);
|
||||
}
|
||||
|
||||
function montarSumulas(sumulas, jogos) {
|
||||
var alvo = document.getElementById("sumulas");
|
||||
limpar(alvo);
|
||||
if (!sumulas || !sumulas.length) { alvo.appendChild(el("p", "explica", "As súmulas dos jogos do seu time aparecem aqui.")); return; }
|
||||
sumulas.forEach(function (s) {
|
||||
var j = (jogos || []).filter(function (x) { return x.ref === s.jogoRef; })[0];
|
||||
var card = el("div", "sumula");
|
||||
var cab = j ? j.mandante + " x " + j.visitante : "Jogo " + s.jogoRef;
|
||||
if (j && j.placarMandante !== undefined && j.placarMandante !== null) {
|
||||
cab = j.mandante + " " + j.placarMandante + " x " + j.placarVisitante + " " + j.visitante;
|
||||
}
|
||||
card.appendChild(el("h3", null, cab));
|
||||
if (j) card.appendChild(el("div", "quando", (j.rodada ? j.rodada + " · " : "") + dataBr(j.data)));
|
||||
secaoDaSumula(card, "Gols", s.gols, function (g) { return momento(g) + " · " + g.time + " · " + pessoa(g.jogador); });
|
||||
secaoDaSumula(card, "Cartões", s.cartoes, function (c) {
|
||||
return momento(c) + " · " + (c.tipo === "VERMELHO" ? "vermelho" : "amarelo") + " · " + c.time + " · " + pessoa(c.jogador);
|
||||
});
|
||||
secaoDaSumula(card, "Substituições", s.substituicoes, function (t) {
|
||||
return momento(t) + " · " + t.time + " · sai " + pessoa(t.sai) + ", entra " + pessoa(t.entra);
|
||||
});
|
||||
alvo.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ ficha
|
||||
|
||||
function linhaDeJogador(j) {
|
||||
var linha = el("div", "linha-ficha linha-jogador");
|
||||
if (j && j.id) linha.setAttribute("data-id", j.id);
|
||||
var nome = el("input");
|
||||
nome.placeholder = "Nome do jogador";
|
||||
nome.maxLength = 120;
|
||||
nome.value = (j && j.nome) || "";
|
||||
nome.setAttribute("aria-label", "Nome do jogador");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
var camisa = el("input");
|
||||
camisa.type = "number";
|
||||
camisa.min = "0";
|
||||
camisa.max = "999";
|
||||
camisa.placeholder = "nº";
|
||||
camisa.value = j && j.numeroCamisa !== undefined && j.numeroCamisa !== null ? j.numeroCamisa : "";
|
||||
camisa.setAttribute("aria-label", "Número da camisa");
|
||||
camisa.setAttribute("data-campo", "camisa");
|
||||
var tirar = el("button", "secundario", "×");
|
||||
tirar.type = "button";
|
||||
tirar.setAttribute("aria-label", "Remover jogador");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(camisa);
|
||||
linha.appendChild(tirar);
|
||||
listaJogadores.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
function linhaDeComissao(m) {
|
||||
var linha = el("div", "linha-ficha linha-comissao");
|
||||
if (m && m.id) linha.setAttribute("data-id", m.id);
|
||||
var nome = el("input");
|
||||
nome.placeholder = "Nome";
|
||||
nome.maxLength = 120;
|
||||
nome.value = (m && m.nome) || "";
|
||||
nome.setAttribute("aria-label", "Nome na comissão técnica");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
var papel = el("input");
|
||||
papel.placeholder = "Função";
|
||||
papel.maxLength = 40;
|
||||
papel.value = (m && m.funcao) || "";
|
||||
papel.setAttribute("list", "funcoes-comissao");
|
||||
papel.setAttribute("aria-label", "Função na comissão técnica");
|
||||
papel.setAttribute("data-campo", "funcao");
|
||||
var tirar = el("button", "secundario", "×");
|
||||
tirar.type = "button";
|
||||
tirar.setAttribute("aria-label", "Remover da comissão");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(papel);
|
||||
linha.appendChild(tirar);
|
||||
listaComissao.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
/** Time da escola ou ficha fechada: só leitura (listas simples). */
|
||||
function fichaSomenteLeitura(ficha) {
|
||||
formFicha.hidden = true;
|
||||
var alvo = document.getElementById("ficha-leitura");
|
||||
if (!alvo) {
|
||||
alvo = el("div", "somente-leitura");
|
||||
alvo.id = "ficha-leitura";
|
||||
formFicha.parentNode.appendChild(alvo);
|
||||
}
|
||||
limpar(alvo);
|
||||
alvo.appendChild(el("h3", null, "Jogadores"));
|
||||
var ul = el("ul");
|
||||
(ficha.jogadores || []).forEach(function (j) {
|
||||
ul.appendChild(el("li", null, pessoa({ nome: j.nome, numeroCamisa: j.numeroCamisa })));
|
||||
});
|
||||
if (!ul.childNodes.length) ul.appendChild(el("li", null, "Nenhum jogador na ficha."));
|
||||
alvo.appendChild(ul);
|
||||
if ((ficha.comissao || []).length) {
|
||||
alvo.appendChild(el("h3", null, "Comissão técnica"));
|
||||
var uc = el("ul");
|
||||
ficha.comissao.forEach(function (m) { uc.appendChild(el("li", null, m.nome + " — " + m.funcao)); });
|
||||
alvo.appendChild(uc);
|
||||
}
|
||||
}
|
||||
|
||||
function montarFicha(ficha) {
|
||||
var aviso = document.getElementById("ficha-aviso");
|
||||
limpar(listaJogadores);
|
||||
limpar(listaComissao);
|
||||
if (!ficha.editavel) {
|
||||
aviso.textContent = "Esta ficha está fechada para alterações aqui. Se algo estiver errado, fale com a organização.";
|
||||
fichaSomenteLeitura(ficha);
|
||||
return;
|
||||
}
|
||||
aviso.textContent = "Confira os jogadores (nome e número da camisa) e a comissão técnica. Dá para corrigir até o primeiro jogo ser encerrado.";
|
||||
formFicha.hidden = false;
|
||||
(ficha.jogadores || []).forEach(linhaDeJogador);
|
||||
if (!(ficha.jogadores || []).length) for (var i = 0; i < 3; i++) linhaDeJogador();
|
||||
(ficha.comissao || []).forEach(linhaDeComissao);
|
||||
}
|
||||
|
||||
function mostrarErroFicha(texto) {
|
||||
fichaErro.textContent = texto || "";
|
||||
fichaErro.hidden = !texto;
|
||||
}
|
||||
|
||||
function mostrarOkFicha(texto) {
|
||||
fichaOk.textContent = texto || "";
|
||||
fichaOk.hidden = !texto;
|
||||
}
|
||||
|
||||
function corpoDaFicha() {
|
||||
var jogadores = [];
|
||||
var linhas = listaJogadores.querySelectorAll(".linha-jogador");
|
||||
for (var i = 0; i < linhas.length; i++) {
|
||||
var nome = linhas[i].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!nome) continue;
|
||||
var c = linhas[i].querySelector('[data-campo="camisa"]').value;
|
||||
var j = { nome: nome, numeroCamisa: c === "" ? null : parseInt(c, 10) };
|
||||
if (linhas[i].getAttribute("data-id")) j.id = linhas[i].getAttribute("data-id");
|
||||
jogadores.push(j);
|
||||
}
|
||||
var comissao = [];
|
||||
var faltaFuncao = false;
|
||||
var pessoas = listaComissao.querySelectorAll(".linha-comissao");
|
||||
for (var k = 0; k < pessoas.length; k++) {
|
||||
var n = pessoas[k].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!n) continue;
|
||||
var f = pessoas[k].querySelector('[data-campo="funcao"]').value.trim();
|
||||
if (!f) faltaFuncao = true;
|
||||
var m = { nome: n, funcao: f };
|
||||
if (pessoas[k].getAttribute("data-id")) m.id = pessoas[k].getAttribute("data-id");
|
||||
comissao.push(m);
|
||||
}
|
||||
return { jogadores: jogadores, comissao: comissao, faltaFuncao: faltaFuncao };
|
||||
}
|
||||
|
||||
function aoSalvar(ev) {
|
||||
ev.preventDefault();
|
||||
mostrarErroFicha("");
|
||||
mostrarOkFicha("");
|
||||
var c = corpoDaFicha();
|
||||
if (c.jogadores.length > MAX_JOGADORES) { mostrarErroFicha("No máximo " + MAX_JOGADORES + " jogadores."); return; }
|
||||
if (c.faltaFuncao) { mostrarErroFicha("Informe a função de cada pessoa da comissão técnica."); return; }
|
||||
if (c.comissao.length > MAX_COMISSAO) { mostrarErroFicha("No máximo " + MAX_COMISSAO + " pessoas na comissão técnica."); return; }
|
||||
salvar.disabled = true;
|
||||
salvar.setAttribute("aria-busy", "true");
|
||||
fetch(base + "/ficha", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ jogadores: c.jogadores, comissao: c.comissao })
|
||||
}).then(lerResposta).then(function (r) {
|
||||
salvar.disabled = false;
|
||||
salvar.removeAttribute("aria-busy");
|
||||
if (r.status >= 200 && r.status < 300) {
|
||||
mostrarOkFicha("Ficha salva.");
|
||||
return carregar(true);
|
||||
}
|
||||
if (r.status === 404) { recusar("Este link não está mais disponível."); return null; }
|
||||
mostrarErroFicha((r.corpo && r.corpo.detail) || "Não foi possível salvar agora. Tente de novo em instantes.");
|
||||
return null;
|
||||
}).catch(function () {
|
||||
salvar.disabled = false;
|
||||
salvar.removeAttribute("aria-busy");
|
||||
mostrarErroFicha("Sem conexão. Confira a internet e tente de novo.");
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ carga
|
||||
|
||||
function mostrar(v, manterAba) {
|
||||
visao = v;
|
||||
var nomeTime = (v.time && v.time.nome) || "Seu time";
|
||||
titulo.textContent = (v.competicao || "Competição") + " — " + nomeTime;
|
||||
document.title = titulo.textContent + " — Athletic Map";
|
||||
var partes = [];
|
||||
if (v.time && v.time.escola) partes.push(v.time.escola);
|
||||
if (v.time && v.time.cidade) partes.push(v.time.cidade);
|
||||
if (v.temporada && v.temporada.nome) partes.push("Temporada " + v.temporada.nome);
|
||||
if (v.time && v.time.status === "DESQUALIFICADO") partes.push("Time desqualificado");
|
||||
situacao.textContent = partes.join(" · ");
|
||||
situacao.className = "";
|
||||
painel.hidden = false;
|
||||
montarTabela(v.classificacao, nomeTime);
|
||||
montarJogos(v.jogos);
|
||||
montarSumulas(v.sumulas, v.jogos);
|
||||
montarFicha(v.ficha || { jogadores: [], comissao: [], editavel: false });
|
||||
if (!manterAba) abrirAba("tabela", false);
|
||||
}
|
||||
|
||||
function carregar(manterAba) {
|
||||
return fetch(base).then(lerResposta).then(function (r) {
|
||||
if (r.status === 404) { recusar("Este link não está disponível ou foi desativado. Peça um novo à organização."); return; }
|
||||
if (r.status === 429) { recusar("Muitas consultas seguidas. Aguarde alguns minutos e abra o link de novo."); return; }
|
||||
if (r.status !== 200) { recusar("Não foi possível abrir agora. Tente de novo em instantes."); return; }
|
||||
mostrar(r.corpo || {}, manterAba);
|
||||
}).catch(function () {
|
||||
recusar("Não foi possível abrir agora. Tente de novo em instantes.");
|
||||
});
|
||||
}
|
||||
|
||||
function iniciar() {
|
||||
if (!TOKEN.test(token)) { recusar("Este endereço não é válido."); return; }
|
||||
ABAS.forEach(function (nome) {
|
||||
var aba = document.getElementById("aba-" + nome);
|
||||
aba.addEventListener("click", function () { abrirAba(nome, false); });
|
||||
aba.addEventListener("keydown", aoTeclarNaAba);
|
||||
});
|
||||
document.getElementById("maisJogador").addEventListener("click", function () {
|
||||
if (listaJogadores.querySelectorAll(".linha-jogador").length < MAX_JOGADORES) linhaDeJogador().querySelector("input").focus();
|
||||
});
|
||||
document.getElementById("maisComissao").addEventListener("click", function () {
|
||||
if (listaComissao.querySelectorAll(".linha-comissao").length < MAX_COMISSAO) linhaDeComissao(null).querySelector("input").focus();
|
||||
});
|
||||
formFicha.addEventListener("submit", aoSalvar);
|
||||
carregar(false);
|
||||
}
|
||||
|
||||
iniciar();
|
||||
})();
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
|
||||
@@ -162,6 +162,9 @@ spec:
|
||||
- { key: "preparando.js", path: "preparando.js" }
|
||||
- { key: "privacidade.html", path: "privacidade.html" }
|
||||
- { key: "termos.html", path: "termos.html" }
|
||||
- { key: "time.css", path: "time.css" }
|
||||
- { key: "time.html", path: "time.html" }
|
||||
- { key: "time.js", path: "time.js" }
|
||||
- configMap:
|
||||
name: frontend-publico-binario
|
||||
items:
|
||||
|
||||
@@ -409,7 +409,7 @@ data:
|
||||
|
||||
<form id="formulario" hidden novalidate>
|
||||
<p class="explica">
|
||||
Preencha os dados do seu time e dos jogadores (só o nome e o número da camisa).
|
||||
Preencha os dados do seu time, dos jogadores (só o nome e o número da camisa) e da comissão técnica (nome e função).
|
||||
A organização confere e aprova antes do time entrar na competição.
|
||||
</p>
|
||||
|
||||
@@ -433,12 +433,23 @@ data:
|
||||
<button type="button" id="maisJogador" class="secundario">+ Jogador</button>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Comissão técnica</legend>
|
||||
<div id="comissao"></div>
|
||||
<datalist id="funcoes-comissao">
|
||||
<option value="Técnico"></option><option value="Auxiliar técnico"></option><option value="Preparador físico"></option>
|
||||
<option value="Preparador de goleiros"></option><option value="Médico"></option><option value="Fisioterapeuta"></option>
|
||||
<option value="Massagista"></option><option value="Roupeiro"></option><option value="Dirigente"></option>
|
||||
</datalist>
|
||||
<button type="button" id="maisComissao" class="secundario">+ Pessoa na comissão</button>
|
||||
</fieldset>
|
||||
|
||||
<p id="erro" class="erro" role="alert" hidden></p>
|
||||
<button type="submit" id="enviar">Enviar time</button>
|
||||
</form>
|
||||
</main>
|
||||
<script src="/publico/desafio.js?v=1"></script>
|
||||
<script src="/publico/convite.js?v=2"></script>
|
||||
<script src="/publico/convite.js?v=3"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -465,6 +476,7 @@ data:
|
||||
|
||||
var TOKEN = /^[A-Za-z0-9_-]{20,64}$/;
|
||||
var MAX_JOGADORES = 60;
|
||||
var MAX_COMISSAO = 15;
|
||||
|
||||
var token = decodeURIComponent((location.pathname.split("/").filter(Boolean).pop() || ""));
|
||||
var base = "/api/public/competicoes/convites/" + encodeURIComponent(token);
|
||||
@@ -473,6 +485,7 @@ data:
|
||||
var situacao = document.getElementById("situacao");
|
||||
var formulario = document.getElementById("formulario");
|
||||
var listaJogadores = document.getElementById("jogadores");
|
||||
var listaComissao = document.getElementById("comissao");
|
||||
var erro = document.getElementById("erro");
|
||||
var enviar = document.getElementById("enviar");
|
||||
var ROTULO_ENVIAR = enviar.textContent;
|
||||
@@ -520,6 +533,39 @@ data:
|
||||
return linha;
|
||||
}
|
||||
|
||||
/** Comissao tecnica (21/10/2026): nome e funcao; a funcao tem sugestoes, mas aceita texto livre. */
|
||||
function linhaDeComissao(funcao) {
|
||||
var linha = document.createElement("div");
|
||||
linha.className = "linha-jogador linha-comissao";
|
||||
|
||||
var nome = document.createElement("input");
|
||||
nome.placeholder = "Nome";
|
||||
nome.maxLength = 120;
|
||||
nome.setAttribute("aria-label", "Nome na comissão técnica");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
|
||||
var papel = document.createElement("input");
|
||||
papel.placeholder = "Função";
|
||||
papel.maxLength = 40;
|
||||
papel.value = funcao || "";
|
||||
papel.setAttribute("list", "funcoes-comissao");
|
||||
papel.setAttribute("aria-label", "Função na comissão técnica");
|
||||
papel.setAttribute("data-campo", "funcao");
|
||||
|
||||
var tirar = document.createElement("button");
|
||||
tirar.type = "button";
|
||||
tirar.className = "secundario";
|
||||
tirar.textContent = "×";
|
||||
tirar.setAttribute("aria-label", "Remover da comissão");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(papel);
|
||||
linha.appendChild(tirar);
|
||||
listaComissao.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
function valor(id) {
|
||||
var v = document.getElementById(id).value.trim();
|
||||
return v === "" ? null : v;
|
||||
@@ -534,14 +580,26 @@ data:
|
||||
var c = linhas[i].querySelector('[data-campo="camisa"]').value;
|
||||
jogadores.push({ nome: nome, numeroCamisa: c === "" ? null : parseInt(c, 10) });
|
||||
}
|
||||
var comissao = [];
|
||||
var faltaFuncao = false;
|
||||
var pessoas = listaComissao.querySelectorAll(".linha-comissao");
|
||||
for (var k = 0; k < pessoas.length; k++) {
|
||||
var n = pessoas[k].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!n) continue;
|
||||
var f = pessoas[k].querySelector('[data-campo="funcao"]').value.trim();
|
||||
if (!f) faltaFuncao = true;
|
||||
comissao.push({ nome: n, funcao: f });
|
||||
}
|
||||
return {
|
||||
faltaFuncao: faltaFuncao,
|
||||
nome: valor("nome"),
|
||||
escola: valor("escola"),
|
||||
cidade: valor("cidade"),
|
||||
contatoNome: valor("contatoNome"),
|
||||
contatoTelefone: valor("contatoTelefone"),
|
||||
contatoEmail: valor("contatoEmail"),
|
||||
jogadores: jogadores
|
||||
jogadores: jogadores,
|
||||
comissao: comissao
|
||||
};
|
||||
}
|
||||
|
||||
@@ -555,6 +613,9 @@ data:
|
||||
var corpo = montarCorpo();
|
||||
if (!corpo.nome) { mostrarErro("Informe o nome do time."); return; }
|
||||
if (corpo.jogadores.length > MAX_JOGADORES) { mostrarErro("No máximo " + MAX_JOGADORES + " jogadores."); return; }
|
||||
if (corpo.faltaFuncao) { mostrarErro("Informe a função de cada pessoa da comissão técnica."); return; }
|
||||
if (corpo.comissao.length > MAX_COMISSAO) { mostrarErro("No máximo " + MAX_COMISSAO + " pessoas na comissão técnica."); return; }
|
||||
delete corpo.faltaFuncao;
|
||||
enviar.disabled = true;
|
||||
enviar.textContent = "Verificando…";
|
||||
enviar.setAttribute("aria-busy", "true");
|
||||
@@ -615,6 +676,7 @@ data:
|
||||
situacao.className = "";
|
||||
formulario.hidden = false;
|
||||
for (var i = 0; i < 5; i++) linhaDeJogador();
|
||||
linhaDeComissao("Técnico");
|
||||
}).catch(function () {
|
||||
recusar("Não foi possível abrir o convite agora. Tente de novo em instantes.");
|
||||
});
|
||||
@@ -623,6 +685,9 @@ data:
|
||||
document.getElementById("maisJogador").addEventListener("click", function () {
|
||||
if (listaJogadores.querySelectorAll(".linha-jogador").length < MAX_JOGADORES) linhaDeJogador().querySelector("input").focus();
|
||||
});
|
||||
document.getElementById("maisComissao").addEventListener("click", function () {
|
||||
if (listaComissao.querySelectorAll(".linha-comissao").length < MAX_COMISSAO) linhaDeComissao("").querySelector("input").focus();
|
||||
});
|
||||
formulario.addEventListener("submit", aoEnviar);
|
||||
iniciar();
|
||||
})();
|
||||
@@ -8242,6 +8307,557 @@ data:
|
||||
</body>
|
||||
</html>
|
||||
|
||||
time.css: |
|
||||
/* Link do time: página pública, sem fonte nem script de fora (a CSP só deixa 'self'). Mobile primeiro. */
|
||||
main { max-width: 56rem; }
|
||||
.explica, .legenda { color: var(--muted); font-size: .9rem; }
|
||||
.abas { display: flex; gap: .25rem; overflow-x: auto; border-bottom: 2px solid var(--line); margin: 0 0 1rem; }
|
||||
.abas button { background: transparent; color: var(--navy); border: 0; border-bottom: 3px solid transparent; border-radius: 0; padding: .6rem .9rem; white-space: nowrap; font: inherit; font-weight: 700; cursor: pointer; }
|
||||
.abas button[aria-selected="true"] { border-bottom-color: var(--orange); }
|
||||
.abas button:focus-visible, button:focus-visible, input:focus-visible { outline: 3px solid var(--orange); outline-offset: 1px; }
|
||||
h2 { font-size: 1.15rem; margin: .5rem 0; }
|
||||
.rolagem { overflow-x: auto; }
|
||||
table { border-collapse: collapse; width: 100%; font-size: .9rem; background: #fff; }
|
||||
th, td { padding: .4rem .45rem; text-align: center; border-bottom: 1px solid var(--line); white-space: nowrap; }
|
||||
th:nth-child(2), td:nth-child(2) { text-align: left; white-space: normal; min-width: 8rem; }
|
||||
tr.meu { background: #fff4e5; font-weight: 700; }
|
||||
.forma { display: inline-flex; gap: 3px; vertical-align: middle; }
|
||||
.ponto { width: .8rem; height: .8rem; border-radius: 50%; display: inline-block; }
|
||||
.ponto.V { background: #067647; }
|
||||
.ponto.E { background: #98a2b3; }
|
||||
.ponto.D { background: #b42318; }
|
||||
.cartao { display: inline-block; width: .7rem; height: .95rem; border-radius: 2px; vertical-align: middle; margin-right: 3px; }
|
||||
.cartao.amarelo { background: #f5c518; }
|
||||
.cartao.vermelho { background: #d92d20; }
|
||||
.jogo, .sumula { background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: .6rem .8rem; margin: 0 0 .6rem; }
|
||||
.jogo .quando { color: var(--muted); font-size: .85rem; }
|
||||
.jogo .confronto { display: grid; grid-template-columns: 1fr auto 1fr; gap: .5rem; align-items: center; font-weight: 700; }
|
||||
.jogo .confronto span:first-child { text-align: right; }
|
||||
.jogo .placar { background: var(--navy); color: #fff; border-radius: 6px; padding: .1rem .6rem; }
|
||||
.etiqueta { display: inline-block; font-size: .75rem; font-weight: 700; padding: .05rem .5rem; border-radius: 99px; background: #fff1e3; border: 1px solid var(--orange); margin-left: .4rem; }
|
||||
.sumula h3 { margin: 0 0 .3rem; font-size: 1rem; }
|
||||
.sumula ul { margin: .2rem 0 .5rem; padding-left: 1.2rem; }
|
||||
form { display: grid; gap: 1rem; }
|
||||
fieldset { border: 1px solid var(--line); border-radius: 8px; background: #fff; padding: .75rem 1rem 1rem; display: grid; gap: .6rem; }
|
||||
legend { font-weight: 700; padding: 0 .4rem; }
|
||||
input { font: inherit; padding: .5rem .6rem; border: 1px solid var(--line); border-radius: 6px; width: 100%; }
|
||||
.linha-ficha { display: grid; grid-template-columns: 1fr 5.5rem auto; gap: .4rem; align-items: center; }
|
||||
.linha-ficha button { padding: .4rem .6rem; }
|
||||
button { font: inherit; font-weight: 700; padding: .65rem 1.1rem; border: 0; border-radius: 8px; background: var(--orange); color: #fff; cursor: pointer; }
|
||||
button.secundario { background: #fff; color: var(--navy); border: 1px solid var(--line); justify-self: start; }
|
||||
button:disabled { opacity: .55; cursor: not-allowed; }
|
||||
.erro { color: #b42318; margin: 0; font-weight: 600; }
|
||||
.ok { background: #e7f6ec; border-left: 4px solid #067647; padding: .8rem 1rem; margin: 0; }
|
||||
#situacao.invalido { background: #fff1e3; border-left: 4px solid var(--orange); padding: 1rem 1.2rem; }
|
||||
.somente-leitura li { margin: .15rem 0; }
|
||||
@media (max-width: 30rem) { .linha-ficha { grid-template-columns: 1fr 4.5rem auto; } }
|
||||
|
||||
time.html: |
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<title>Seu time na competição — Athletic Map</title>
|
||||
<link rel="stylesheet" href="/publico/_estilo.css">
|
||||
<link rel="stylesheet" href="/publico/time.css?v=1">
|
||||
</head>
|
||||
<body>
|
||||
<header><div class="marca">Athletic <span>Map</span></div></header>
|
||||
<main>
|
||||
<h1 id="titulo">Seu time na competição</h1>
|
||||
<p id="situacao" role="status" aria-live="polite">Abrindo o link…</p>
|
||||
|
||||
<div id="painel" hidden>
|
||||
<div class="abas" role="tablist" aria-label="Seções">
|
||||
<button type="button" role="tab" id="aba-tabela" aria-controls="sec-tabela" aria-selected="true" data-aba="tabela">Tabela</button>
|
||||
<button type="button" role="tab" id="aba-jogos" aria-controls="sec-jogos" aria-selected="false" tabindex="-1" data-aba="jogos">Jogos</button>
|
||||
<button type="button" role="tab" id="aba-sumulas" aria-controls="sec-sumulas" aria-selected="false" tabindex="-1" data-aba="sumulas">Súmulas</button>
|
||||
<button type="button" role="tab" id="aba-ficha" aria-controls="sec-ficha" aria-selected="false" tabindex="-1" data-aba="ficha">Ficha do time</button>
|
||||
</div>
|
||||
|
||||
<section id="sec-tabela" role="tabpanel" aria-labelledby="aba-tabela">
|
||||
<h2>Tabela</h2>
|
||||
<div class="rolagem" id="tabela"></div>
|
||||
<p class="legenda">P pontos · J jogos · V vitórias · E empates · D derrotas · GP gols pró · GC gols contra · SG saldo de gols · % aproveitamento · CA cartões amarelos · CV cartões vermelhos</p>
|
||||
</section>
|
||||
|
||||
<section id="sec-jogos" role="tabpanel" aria-labelledby="aba-jogos" hidden>
|
||||
<h2>Jogos</h2>
|
||||
<div id="jogos"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-sumulas" role="tabpanel" aria-labelledby="aba-sumulas" hidden>
|
||||
<h2>Súmulas</h2>
|
||||
<div id="sumulas"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-ficha" role="tabpanel" aria-labelledby="aba-ficha" hidden>
|
||||
<h2>Ficha do time</h2>
|
||||
<p id="ficha-aviso" class="explica"></p>
|
||||
<form id="ficha" novalidate>
|
||||
<fieldset>
|
||||
<legend>Jogadores</legend>
|
||||
<div id="jogadores"></div>
|
||||
<button type="button" id="maisJogador" class="secundario">+ Jogador</button>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Comissão técnica</legend>
|
||||
<div id="comissao"></div>
|
||||
<datalist id="funcoes-comissao">
|
||||
<option value="Técnico"></option><option value="Auxiliar técnico"></option><option value="Preparador físico"></option>
|
||||
<option value="Preparador de goleiros"></option><option value="Médico"></option><option value="Fisioterapeuta"></option>
|
||||
<option value="Massagista"></option><option value="Roupeiro"></option><option value="Dirigente"></option>
|
||||
</datalist>
|
||||
<button type="button" id="maisComissao" class="secundario">+ Pessoa na comissão</button>
|
||||
</fieldset>
|
||||
<p id="ficha-erro" class="erro" role="alert" hidden></p>
|
||||
<p id="ficha-ok" class="ok" role="status" hidden></p>
|
||||
<button type="submit" id="salvar">Salvar ficha</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<script src="/publico/time.js?v=1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
time.js: |
|
||||
/* ===========================================================================
|
||||
* Link do time — a página que UMA pessoa do time abre pelo link com chave.
|
||||
*
|
||||
* Sem login: a chave no endereço é o único segredo. A página mostra a tabela,
|
||||
* os jogos e as súmulas da competição e a ficha do time; se o time for de
|
||||
* fora e ainda não houver jogo encerrado, a ficha pode ser corrigida aqui.
|
||||
*
|
||||
* Tudo o que vem do servidor entra na página com `textContent` — nunca como
|
||||
* HTML. O script é externo porque a CSP é `script-src 'self'`.
|
||||
* =========================================================================== */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var TOKEN = /^[A-Za-z0-9_-]{20,64}$/;
|
||||
var MAX_JOGADORES = 60;
|
||||
var MAX_COMISSAO = 15;
|
||||
var ABAS = ["tabela", "jogos", "sumulas", "ficha"];
|
||||
var ROTULOS_FORMA = { V: "Vitória", E: "Empate", D: "Derrota" };
|
||||
var ROTULOS_STATUS = {
|
||||
AGENDADA: "Agendado", ADIADA: "Adiado", EM_ANDAMENTO: "Em andamento", ENCERRADA: "Encerrado", CANCELADA: "Cancelado"
|
||||
};
|
||||
|
||||
var token = decodeURIComponent(location.pathname.split("/").filter(Boolean).pop() || "");
|
||||
var base = "/api/public/competicoes/times/" + encodeURIComponent(token);
|
||||
|
||||
var titulo = document.getElementById("titulo");
|
||||
var situacao = document.getElementById("situacao");
|
||||
var painel = document.getElementById("painel");
|
||||
var listaJogadores = document.getElementById("jogadores");
|
||||
var listaComissao = document.getElementById("comissao");
|
||||
var formFicha = document.getElementById("ficha");
|
||||
var fichaErro = document.getElementById("ficha-erro");
|
||||
var fichaOk = document.getElementById("ficha-ok");
|
||||
var salvar = document.getElementById("salvar");
|
||||
var visao = null;
|
||||
|
||||
function el(tag, classe, texto) {
|
||||
var e = document.createElement(tag);
|
||||
if (classe) e.className = classe;
|
||||
if (texto !== undefined && texto !== null) e.textContent = String(texto);
|
||||
return e;
|
||||
}
|
||||
|
||||
function limpar(no) {
|
||||
while (no.firstChild) no.removeChild(no.firstChild);
|
||||
}
|
||||
|
||||
function recusar(texto) {
|
||||
situacao.textContent = texto;
|
||||
situacao.className = "invalido";
|
||||
painel.hidden = true;
|
||||
}
|
||||
|
||||
function lerResposta(r) {
|
||||
return r.json().catch(function () { return {}; }).then(function (corpo) { return { status: r.status, corpo: corpo }; });
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ abas
|
||||
|
||||
function abrirAba(nome, focar) {
|
||||
for (var i = 0; i < ABAS.length; i++) {
|
||||
var ativa = ABAS[i] === nome;
|
||||
var aba = document.getElementById("aba-" + ABAS[i]);
|
||||
aba.setAttribute("aria-selected", ativa ? "true" : "false");
|
||||
aba.tabIndex = ativa ? 0 : -1;
|
||||
document.getElementById("sec-" + ABAS[i]).hidden = !ativa;
|
||||
if (ativa && focar) aba.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function aoTeclarNaAba(ev) {
|
||||
var i = ABAS.indexOf(ev.currentTarget.getAttribute("data-aba"));
|
||||
if (ev.key === "ArrowRight") { abrirAba(ABAS[(i + 1) % ABAS.length], true); ev.preventDefault(); }
|
||||
else if (ev.key === "ArrowLeft") { abrirAba(ABAS[(i + ABAS.length - 1) % ABAS.length], true); ev.preventDefault(); }
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ tabela
|
||||
|
||||
function dataBr(iso) {
|
||||
if (!iso) return "Data a definir";
|
||||
var d = new Date(iso);
|
||||
if (isNaN(d.getTime())) return "Data a definir";
|
||||
try {
|
||||
return d.toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" });
|
||||
} catch (e) {
|
||||
return d.toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
function percentual(n) {
|
||||
return (Math.round((n || 0) * 10) / 10).toString().replace(".", ",") + "%";
|
||||
}
|
||||
|
||||
function montarTabela(linhas, meuNome) {
|
||||
var alvo = document.getElementById("tabela");
|
||||
limpar(alvo);
|
||||
if (!linhas || !linhas.length) { alvo.appendChild(el("p", "explica", "A tabela aparece quando houver times e jogos.")); return; }
|
||||
var t = el("table");
|
||||
t.appendChild(el("caption", "explica", "Classificação"));
|
||||
var cab = el("tr");
|
||||
var cols = [["#", "Posição"], ["Time", "Time"], ["P", "Pontos"], ["J", "Jogos"], ["V", "Vitórias"], ["E", "Empates"],
|
||||
["D", "Derrotas"], ["GP", "Gols pró"], ["GC", "Gols contra"], ["SG", "Saldo de gols"], ["%", "Aproveitamento"],
|
||||
["Últimos 5", "Últimos 5 jogos"], ["CA", "Cartões amarelos"], ["CV", "Cartões vermelhos"]];
|
||||
cols.forEach(function (c) {
|
||||
var th = el("th", null, c[0]);
|
||||
th.scope = "col";
|
||||
th.title = c[1];
|
||||
cab.appendChild(th);
|
||||
});
|
||||
var thead = el("thead");
|
||||
thead.appendChild(cab);
|
||||
t.appendChild(thead);
|
||||
var corpo = el("tbody");
|
||||
linhas.forEach(function (l) {
|
||||
var tr = el("tr", l.time === meuNome ? "meu" : "");
|
||||
[l.posicao, l.time, l.pontos, l.jogos, l.vitorias, l.empates, l.derrotas, l.golsPro, l.golsContra, l.saldoGols,
|
||||
percentual(l.aproveitamento)].forEach(function (v) { tr.appendChild(el("td", null, v)); });
|
||||
var forma = el("td");
|
||||
var caixa = el("span", "forma");
|
||||
(l.ultimos || []).forEach(function (r) {
|
||||
var p = el("span", "ponto " + r);
|
||||
p.title = ROTULOS_FORMA[r] || r;
|
||||
p.setAttribute("role", "img");
|
||||
p.setAttribute("aria-label", ROTULOS_FORMA[r] || r);
|
||||
caixa.appendChild(p);
|
||||
});
|
||||
forma.appendChild(caixa);
|
||||
tr.appendChild(forma);
|
||||
var am = el("td");
|
||||
am.appendChild(el("span", "cartao amarelo"));
|
||||
am.appendChild(document.createTextNode(String(l.amarelos || 0)));
|
||||
tr.appendChild(am);
|
||||
var vm = el("td");
|
||||
vm.appendChild(el("span", "cartao vermelho"));
|
||||
vm.appendChild(document.createTextNode(String(l.vermelhos || 0)));
|
||||
tr.appendChild(vm);
|
||||
corpo.appendChild(tr);
|
||||
});
|
||||
t.appendChild(corpo);
|
||||
alvo.appendChild(t);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ jogos
|
||||
|
||||
function montarJogos(jogos) {
|
||||
var alvo = document.getElementById("jogos");
|
||||
limpar(alvo);
|
||||
if (!jogos || !jogos.length) { alvo.appendChild(el("p", "explica", "Ainda não há jogos marcados.")); return; }
|
||||
jogos.forEach(function (j) {
|
||||
var card = el("div", "jogo");
|
||||
var quando = el("div", "quando", (j.rodada ? j.rodada + " · " : "") + dataBr(j.data));
|
||||
var etiqueta = el("span", "etiqueta", ROTULOS_STATUS[j.status] || j.status);
|
||||
quando.appendChild(etiqueta);
|
||||
if (j.wo) quando.appendChild(el("span", "etiqueta", "W.O."));
|
||||
card.appendChild(quando);
|
||||
var linha = el("div", "confronto");
|
||||
linha.appendChild(el("span", null, j.mandante));
|
||||
var temPlacar = j.placarMandante !== undefined && j.placarMandante !== null;
|
||||
linha.appendChild(el("span", temPlacar ? "placar" : "", temPlacar ? j.placarMandante + " x " + j.placarVisitante : "x"));
|
||||
linha.appendChild(el("span", null, j.visitante));
|
||||
card.appendChild(linha);
|
||||
if (j.penaltis) card.appendChild(el("div", "quando", "Pênaltis: " + j.penaltis.mandante + " x " + j.penaltis.visitante));
|
||||
if (j.motivo) card.appendChild(el("div", "quando", "Motivo: " + j.motivo));
|
||||
alvo.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ súmulas
|
||||
|
||||
function pessoa(p) {
|
||||
if (!p) return "jogador não identificado";
|
||||
if (p.nome && p.numeroCamisa !== undefined && p.numeroCamisa !== null) return p.nome + " (" + p.numeroCamisa + ")";
|
||||
if (p.nome) return p.nome;
|
||||
if (p.numeroCamisa !== undefined && p.numeroCamisa !== null) return "camisa " + p.numeroCamisa;
|
||||
return "jogador não identificado";
|
||||
}
|
||||
|
||||
function momento(e) {
|
||||
var m = e.minuto !== undefined && e.minuto !== null ? e.minuto + "'" : "";
|
||||
var t = e.tempo ? e.tempo + "º tempo" : "";
|
||||
return [t, m].filter(Boolean).join(" ") || "—";
|
||||
}
|
||||
|
||||
function secaoDaSumula(card, nome, itens, texto) {
|
||||
if (!itens || !itens.length) return;
|
||||
card.appendChild(el("strong", null, nome));
|
||||
var ul = el("ul");
|
||||
itens.forEach(function (i) { ul.appendChild(el("li", null, texto(i))); });
|
||||
card.appendChild(ul);
|
||||
}
|
||||
|
||||
function montarSumulas(sumulas, jogos) {
|
||||
var alvo = document.getElementById("sumulas");
|
||||
limpar(alvo);
|
||||
if (!sumulas || !sumulas.length) { alvo.appendChild(el("p", "explica", "As súmulas dos jogos do seu time aparecem aqui.")); return; }
|
||||
sumulas.forEach(function (s) {
|
||||
var j = (jogos || []).filter(function (x) { return x.ref === s.jogoRef; })[0];
|
||||
var card = el("div", "sumula");
|
||||
var cab = j ? j.mandante + " x " + j.visitante : "Jogo " + s.jogoRef;
|
||||
if (j && j.placarMandante !== undefined && j.placarMandante !== null) {
|
||||
cab = j.mandante + " " + j.placarMandante + " x " + j.placarVisitante + " " + j.visitante;
|
||||
}
|
||||
card.appendChild(el("h3", null, cab));
|
||||
if (j) card.appendChild(el("div", "quando", (j.rodada ? j.rodada + " · " : "") + dataBr(j.data)));
|
||||
secaoDaSumula(card, "Gols", s.gols, function (g) { return momento(g) + " · " + g.time + " · " + pessoa(g.jogador); });
|
||||
secaoDaSumula(card, "Cartões", s.cartoes, function (c) {
|
||||
return momento(c) + " · " + (c.tipo === "VERMELHO" ? "vermelho" : "amarelo") + " · " + c.time + " · " + pessoa(c.jogador);
|
||||
});
|
||||
secaoDaSumula(card, "Substituições", s.substituicoes, function (t) {
|
||||
return momento(t) + " · " + t.time + " · sai " + pessoa(t.sai) + ", entra " + pessoa(t.entra);
|
||||
});
|
||||
alvo.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ ficha
|
||||
|
||||
function linhaDeJogador(j) {
|
||||
var linha = el("div", "linha-ficha linha-jogador");
|
||||
if (j && j.id) linha.setAttribute("data-id", j.id);
|
||||
var nome = el("input");
|
||||
nome.placeholder = "Nome do jogador";
|
||||
nome.maxLength = 120;
|
||||
nome.value = (j && j.nome) || "";
|
||||
nome.setAttribute("aria-label", "Nome do jogador");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
var camisa = el("input");
|
||||
camisa.type = "number";
|
||||
camisa.min = "0";
|
||||
camisa.max = "999";
|
||||
camisa.placeholder = "nº";
|
||||
camisa.value = j && j.numeroCamisa !== undefined && j.numeroCamisa !== null ? j.numeroCamisa : "";
|
||||
camisa.setAttribute("aria-label", "Número da camisa");
|
||||
camisa.setAttribute("data-campo", "camisa");
|
||||
var tirar = el("button", "secundario", "×");
|
||||
tirar.type = "button";
|
||||
tirar.setAttribute("aria-label", "Remover jogador");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(camisa);
|
||||
linha.appendChild(tirar);
|
||||
listaJogadores.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
function linhaDeComissao(m) {
|
||||
var linha = el("div", "linha-ficha linha-comissao");
|
||||
if (m && m.id) linha.setAttribute("data-id", m.id);
|
||||
var nome = el("input");
|
||||
nome.placeholder = "Nome";
|
||||
nome.maxLength = 120;
|
||||
nome.value = (m && m.nome) || "";
|
||||
nome.setAttribute("aria-label", "Nome na comissão técnica");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
var papel = el("input");
|
||||
papel.placeholder = "Função";
|
||||
papel.maxLength = 40;
|
||||
papel.value = (m && m.funcao) || "";
|
||||
papel.setAttribute("list", "funcoes-comissao");
|
||||
papel.setAttribute("aria-label", "Função na comissão técnica");
|
||||
papel.setAttribute("data-campo", "funcao");
|
||||
var tirar = el("button", "secundario", "×");
|
||||
tirar.type = "button";
|
||||
tirar.setAttribute("aria-label", "Remover da comissão");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(papel);
|
||||
linha.appendChild(tirar);
|
||||
listaComissao.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
/** Time da escola ou ficha fechada: só leitura (listas simples). */
|
||||
function fichaSomenteLeitura(ficha) {
|
||||
formFicha.hidden = true;
|
||||
var alvo = document.getElementById("ficha-leitura");
|
||||
if (!alvo) {
|
||||
alvo = el("div", "somente-leitura");
|
||||
alvo.id = "ficha-leitura";
|
||||
formFicha.parentNode.appendChild(alvo);
|
||||
}
|
||||
limpar(alvo);
|
||||
alvo.appendChild(el("h3", null, "Jogadores"));
|
||||
var ul = el("ul");
|
||||
(ficha.jogadores || []).forEach(function (j) {
|
||||
ul.appendChild(el("li", null, pessoa({ nome: j.nome, numeroCamisa: j.numeroCamisa })));
|
||||
});
|
||||
if (!ul.childNodes.length) ul.appendChild(el("li", null, "Nenhum jogador na ficha."));
|
||||
alvo.appendChild(ul);
|
||||
if ((ficha.comissao || []).length) {
|
||||
alvo.appendChild(el("h3", null, "Comissão técnica"));
|
||||
var uc = el("ul");
|
||||
ficha.comissao.forEach(function (m) { uc.appendChild(el("li", null, m.nome + " — " + m.funcao)); });
|
||||
alvo.appendChild(uc);
|
||||
}
|
||||
}
|
||||
|
||||
function montarFicha(ficha) {
|
||||
var aviso = document.getElementById("ficha-aviso");
|
||||
limpar(listaJogadores);
|
||||
limpar(listaComissao);
|
||||
if (!ficha.editavel) {
|
||||
aviso.textContent = "Esta ficha está fechada para alterações aqui. Se algo estiver errado, fale com a organização.";
|
||||
fichaSomenteLeitura(ficha);
|
||||
return;
|
||||
}
|
||||
aviso.textContent = "Confira os jogadores (nome e número da camisa) e a comissão técnica. Dá para corrigir até o primeiro jogo ser encerrado.";
|
||||
formFicha.hidden = false;
|
||||
(ficha.jogadores || []).forEach(linhaDeJogador);
|
||||
if (!(ficha.jogadores || []).length) for (var i = 0; i < 3; i++) linhaDeJogador();
|
||||
(ficha.comissao || []).forEach(linhaDeComissao);
|
||||
}
|
||||
|
||||
function mostrarErroFicha(texto) {
|
||||
fichaErro.textContent = texto || "";
|
||||
fichaErro.hidden = !texto;
|
||||
}
|
||||
|
||||
function mostrarOkFicha(texto) {
|
||||
fichaOk.textContent = texto || "";
|
||||
fichaOk.hidden = !texto;
|
||||
}
|
||||
|
||||
function corpoDaFicha() {
|
||||
var jogadores = [];
|
||||
var linhas = listaJogadores.querySelectorAll(".linha-jogador");
|
||||
for (var i = 0; i < linhas.length; i++) {
|
||||
var nome = linhas[i].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!nome) continue;
|
||||
var c = linhas[i].querySelector('[data-campo="camisa"]').value;
|
||||
var j = { nome: nome, numeroCamisa: c === "" ? null : parseInt(c, 10) };
|
||||
if (linhas[i].getAttribute("data-id")) j.id = linhas[i].getAttribute("data-id");
|
||||
jogadores.push(j);
|
||||
}
|
||||
var comissao = [];
|
||||
var faltaFuncao = false;
|
||||
var pessoas = listaComissao.querySelectorAll(".linha-comissao");
|
||||
for (var k = 0; k < pessoas.length; k++) {
|
||||
var n = pessoas[k].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!n) continue;
|
||||
var f = pessoas[k].querySelector('[data-campo="funcao"]').value.trim();
|
||||
if (!f) faltaFuncao = true;
|
||||
var m = { nome: n, funcao: f };
|
||||
if (pessoas[k].getAttribute("data-id")) m.id = pessoas[k].getAttribute("data-id");
|
||||
comissao.push(m);
|
||||
}
|
||||
return { jogadores: jogadores, comissao: comissao, faltaFuncao: faltaFuncao };
|
||||
}
|
||||
|
||||
function aoSalvar(ev) {
|
||||
ev.preventDefault();
|
||||
mostrarErroFicha("");
|
||||
mostrarOkFicha("");
|
||||
var c = corpoDaFicha();
|
||||
if (c.jogadores.length > MAX_JOGADORES) { mostrarErroFicha("No máximo " + MAX_JOGADORES + " jogadores."); return; }
|
||||
if (c.faltaFuncao) { mostrarErroFicha("Informe a função de cada pessoa da comissão técnica."); return; }
|
||||
if (c.comissao.length > MAX_COMISSAO) { mostrarErroFicha("No máximo " + MAX_COMISSAO + " pessoas na comissão técnica."); return; }
|
||||
salvar.disabled = true;
|
||||
salvar.setAttribute("aria-busy", "true");
|
||||
fetch(base + "/ficha", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ jogadores: c.jogadores, comissao: c.comissao })
|
||||
}).then(lerResposta).then(function (r) {
|
||||
salvar.disabled = false;
|
||||
salvar.removeAttribute("aria-busy");
|
||||
if (r.status >= 200 && r.status < 300) {
|
||||
mostrarOkFicha("Ficha salva.");
|
||||
return carregar(true);
|
||||
}
|
||||
if (r.status === 404) { recusar("Este link não está mais disponível."); return null; }
|
||||
mostrarErroFicha((r.corpo && r.corpo.detail) || "Não foi possível salvar agora. Tente de novo em instantes.");
|
||||
return null;
|
||||
}).catch(function () {
|
||||
salvar.disabled = false;
|
||||
salvar.removeAttribute("aria-busy");
|
||||
mostrarErroFicha("Sem conexão. Confira a internet e tente de novo.");
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ carga
|
||||
|
||||
function mostrar(v, manterAba) {
|
||||
visao = v;
|
||||
var nomeTime = (v.time && v.time.nome) || "Seu time";
|
||||
titulo.textContent = (v.competicao || "Competição") + " — " + nomeTime;
|
||||
document.title = titulo.textContent + " — Athletic Map";
|
||||
var partes = [];
|
||||
if (v.time && v.time.escola) partes.push(v.time.escola);
|
||||
if (v.time && v.time.cidade) partes.push(v.time.cidade);
|
||||
if (v.temporada && v.temporada.nome) partes.push("Temporada " + v.temporada.nome);
|
||||
if (v.time && v.time.status === "DESQUALIFICADO") partes.push("Time desqualificado");
|
||||
situacao.textContent = partes.join(" · ");
|
||||
situacao.className = "";
|
||||
painel.hidden = false;
|
||||
montarTabela(v.classificacao, nomeTime);
|
||||
montarJogos(v.jogos);
|
||||
montarSumulas(v.sumulas, v.jogos);
|
||||
montarFicha(v.ficha || { jogadores: [], comissao: [], editavel: false });
|
||||
if (!manterAba) abrirAba("tabela", false);
|
||||
}
|
||||
|
||||
function carregar(manterAba) {
|
||||
return fetch(base).then(lerResposta).then(function (r) {
|
||||
if (r.status === 404) { recusar("Este link não está disponível ou foi desativado. Peça um novo à organização."); return; }
|
||||
if (r.status === 429) { recusar("Muitas consultas seguidas. Aguarde alguns minutos e abra o link de novo."); return; }
|
||||
if (r.status !== 200) { recusar("Não foi possível abrir agora. Tente de novo em instantes."); return; }
|
||||
mostrar(r.corpo || {}, manterAba);
|
||||
}).catch(function () {
|
||||
recusar("Não foi possível abrir agora. Tente de novo em instantes.");
|
||||
});
|
||||
}
|
||||
|
||||
function iniciar() {
|
||||
if (!TOKEN.test(token)) { recusar("Este endereço não é válido."); return; }
|
||||
ABAS.forEach(function (nome) {
|
||||
var aba = document.getElementById("aba-" + nome);
|
||||
aba.addEventListener("click", function () { abrirAba(nome, false); });
|
||||
aba.addEventListener("keydown", aoTeclarNaAba);
|
||||
});
|
||||
document.getElementById("maisJogador").addEventListener("click", function () {
|
||||
if (listaJogadores.querySelectorAll(".linha-jogador").length < MAX_JOGADORES) linhaDeJogador().querySelector("input").focus();
|
||||
});
|
||||
document.getElementById("maisComissao").addEventListener("click", function () {
|
||||
if (listaComissao.querySelectorAll(".linha-comissao").length < MAX_COMISSAO) linhaDeComissao(null).querySelector("input").focus();
|
||||
});
|
||||
formFicha.addEventListener("submit", aoSalvar);
|
||||
carregar(false);
|
||||
}
|
||||
|
||||
iniciar();
|
||||
})();
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
|
||||
@@ -162,6 +162,9 @@ spec:
|
||||
- { key: "preparando.js", path: "preparando.js" }
|
||||
- { key: "privacidade.html", path: "privacidade.html" }
|
||||
- { key: "termos.html", path: "termos.html" }
|
||||
- { key: "time.css", path: "time.css" }
|
||||
- { key: "time.html", path: "time.html" }
|
||||
- { key: "time.js", path: "time.js" }
|
||||
- configMap:
|
||||
name: frontend-publico-binario
|
||||
items:
|
||||
|
||||
@@ -409,7 +409,7 @@ data:
|
||||
|
||||
<form id="formulario" hidden novalidate>
|
||||
<p class="explica">
|
||||
Preencha os dados do seu time e dos jogadores (só o nome e o número da camisa).
|
||||
Preencha os dados do seu time, dos jogadores (só o nome e o número da camisa) e da comissão técnica (nome e função).
|
||||
A organização confere e aprova antes do time entrar na competição.
|
||||
</p>
|
||||
|
||||
@@ -433,12 +433,23 @@ data:
|
||||
<button type="button" id="maisJogador" class="secundario">+ Jogador</button>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Comissão técnica</legend>
|
||||
<div id="comissao"></div>
|
||||
<datalist id="funcoes-comissao">
|
||||
<option value="Técnico"></option><option value="Auxiliar técnico"></option><option value="Preparador físico"></option>
|
||||
<option value="Preparador de goleiros"></option><option value="Médico"></option><option value="Fisioterapeuta"></option>
|
||||
<option value="Massagista"></option><option value="Roupeiro"></option><option value="Dirigente"></option>
|
||||
</datalist>
|
||||
<button type="button" id="maisComissao" class="secundario">+ Pessoa na comissão</button>
|
||||
</fieldset>
|
||||
|
||||
<p id="erro" class="erro" role="alert" hidden></p>
|
||||
<button type="submit" id="enviar">Enviar time</button>
|
||||
</form>
|
||||
</main>
|
||||
<script src="/publico/desafio.js?v=1"></script>
|
||||
<script src="/publico/convite.js?v=2"></script>
|
||||
<script src="/publico/convite.js?v=3"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -465,6 +476,7 @@ data:
|
||||
|
||||
var TOKEN = /^[A-Za-z0-9_-]{20,64}$/;
|
||||
var MAX_JOGADORES = 60;
|
||||
var MAX_COMISSAO = 15;
|
||||
|
||||
var token = decodeURIComponent((location.pathname.split("/").filter(Boolean).pop() || ""));
|
||||
var base = "/api/public/competicoes/convites/" + encodeURIComponent(token);
|
||||
@@ -473,6 +485,7 @@ data:
|
||||
var situacao = document.getElementById("situacao");
|
||||
var formulario = document.getElementById("formulario");
|
||||
var listaJogadores = document.getElementById("jogadores");
|
||||
var listaComissao = document.getElementById("comissao");
|
||||
var erro = document.getElementById("erro");
|
||||
var enviar = document.getElementById("enviar");
|
||||
var ROTULO_ENVIAR = enviar.textContent;
|
||||
@@ -520,6 +533,39 @@ data:
|
||||
return linha;
|
||||
}
|
||||
|
||||
/** Comissao tecnica (21/10/2026): nome e funcao; a funcao tem sugestoes, mas aceita texto livre. */
|
||||
function linhaDeComissao(funcao) {
|
||||
var linha = document.createElement("div");
|
||||
linha.className = "linha-jogador linha-comissao";
|
||||
|
||||
var nome = document.createElement("input");
|
||||
nome.placeholder = "Nome";
|
||||
nome.maxLength = 120;
|
||||
nome.setAttribute("aria-label", "Nome na comissão técnica");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
|
||||
var papel = document.createElement("input");
|
||||
papel.placeholder = "Função";
|
||||
papel.maxLength = 40;
|
||||
papel.value = funcao || "";
|
||||
papel.setAttribute("list", "funcoes-comissao");
|
||||
papel.setAttribute("aria-label", "Função na comissão técnica");
|
||||
papel.setAttribute("data-campo", "funcao");
|
||||
|
||||
var tirar = document.createElement("button");
|
||||
tirar.type = "button";
|
||||
tirar.className = "secundario";
|
||||
tirar.textContent = "×";
|
||||
tirar.setAttribute("aria-label", "Remover da comissão");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(papel);
|
||||
linha.appendChild(tirar);
|
||||
listaComissao.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
function valor(id) {
|
||||
var v = document.getElementById(id).value.trim();
|
||||
return v === "" ? null : v;
|
||||
@@ -534,14 +580,26 @@ data:
|
||||
var c = linhas[i].querySelector('[data-campo="camisa"]').value;
|
||||
jogadores.push({ nome: nome, numeroCamisa: c === "" ? null : parseInt(c, 10) });
|
||||
}
|
||||
var comissao = [];
|
||||
var faltaFuncao = false;
|
||||
var pessoas = listaComissao.querySelectorAll(".linha-comissao");
|
||||
for (var k = 0; k < pessoas.length; k++) {
|
||||
var n = pessoas[k].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!n) continue;
|
||||
var f = pessoas[k].querySelector('[data-campo="funcao"]').value.trim();
|
||||
if (!f) faltaFuncao = true;
|
||||
comissao.push({ nome: n, funcao: f });
|
||||
}
|
||||
return {
|
||||
faltaFuncao: faltaFuncao,
|
||||
nome: valor("nome"),
|
||||
escola: valor("escola"),
|
||||
cidade: valor("cidade"),
|
||||
contatoNome: valor("contatoNome"),
|
||||
contatoTelefone: valor("contatoTelefone"),
|
||||
contatoEmail: valor("contatoEmail"),
|
||||
jogadores: jogadores
|
||||
jogadores: jogadores,
|
||||
comissao: comissao
|
||||
};
|
||||
}
|
||||
|
||||
@@ -555,6 +613,9 @@ data:
|
||||
var corpo = montarCorpo();
|
||||
if (!corpo.nome) { mostrarErro("Informe o nome do time."); return; }
|
||||
if (corpo.jogadores.length > MAX_JOGADORES) { mostrarErro("No máximo " + MAX_JOGADORES + " jogadores."); return; }
|
||||
if (corpo.faltaFuncao) { mostrarErro("Informe a função de cada pessoa da comissão técnica."); return; }
|
||||
if (corpo.comissao.length > MAX_COMISSAO) { mostrarErro("No máximo " + MAX_COMISSAO + " pessoas na comissão técnica."); return; }
|
||||
delete corpo.faltaFuncao;
|
||||
enviar.disabled = true;
|
||||
enviar.textContent = "Verificando…";
|
||||
enviar.setAttribute("aria-busy", "true");
|
||||
@@ -615,6 +676,7 @@ data:
|
||||
situacao.className = "";
|
||||
formulario.hidden = false;
|
||||
for (var i = 0; i < 5; i++) linhaDeJogador();
|
||||
linhaDeComissao("Técnico");
|
||||
}).catch(function () {
|
||||
recusar("Não foi possível abrir o convite agora. Tente de novo em instantes.");
|
||||
});
|
||||
@@ -623,6 +685,9 @@ data:
|
||||
document.getElementById("maisJogador").addEventListener("click", function () {
|
||||
if (listaJogadores.querySelectorAll(".linha-jogador").length < MAX_JOGADORES) linhaDeJogador().querySelector("input").focus();
|
||||
});
|
||||
document.getElementById("maisComissao").addEventListener("click", function () {
|
||||
if (listaComissao.querySelectorAll(".linha-comissao").length < MAX_COMISSAO) linhaDeComissao("").querySelector("input").focus();
|
||||
});
|
||||
formulario.addEventListener("submit", aoEnviar);
|
||||
iniciar();
|
||||
})();
|
||||
@@ -8242,6 +8307,557 @@ data:
|
||||
</body>
|
||||
</html>
|
||||
|
||||
time.css: |
|
||||
/* Link do time: página pública, sem fonte nem script de fora (a CSP só deixa 'self'). Mobile primeiro. */
|
||||
main { max-width: 56rem; }
|
||||
.explica, .legenda { color: var(--muted); font-size: .9rem; }
|
||||
.abas { display: flex; gap: .25rem; overflow-x: auto; border-bottom: 2px solid var(--line); margin: 0 0 1rem; }
|
||||
.abas button { background: transparent; color: var(--navy); border: 0; border-bottom: 3px solid transparent; border-radius: 0; padding: .6rem .9rem; white-space: nowrap; font: inherit; font-weight: 700; cursor: pointer; }
|
||||
.abas button[aria-selected="true"] { border-bottom-color: var(--orange); }
|
||||
.abas button:focus-visible, button:focus-visible, input:focus-visible { outline: 3px solid var(--orange); outline-offset: 1px; }
|
||||
h2 { font-size: 1.15rem; margin: .5rem 0; }
|
||||
.rolagem { overflow-x: auto; }
|
||||
table { border-collapse: collapse; width: 100%; font-size: .9rem; background: #fff; }
|
||||
th, td { padding: .4rem .45rem; text-align: center; border-bottom: 1px solid var(--line); white-space: nowrap; }
|
||||
th:nth-child(2), td:nth-child(2) { text-align: left; white-space: normal; min-width: 8rem; }
|
||||
tr.meu { background: #fff4e5; font-weight: 700; }
|
||||
.forma { display: inline-flex; gap: 3px; vertical-align: middle; }
|
||||
.ponto { width: .8rem; height: .8rem; border-radius: 50%; display: inline-block; }
|
||||
.ponto.V { background: #067647; }
|
||||
.ponto.E { background: #98a2b3; }
|
||||
.ponto.D { background: #b42318; }
|
||||
.cartao { display: inline-block; width: .7rem; height: .95rem; border-radius: 2px; vertical-align: middle; margin-right: 3px; }
|
||||
.cartao.amarelo { background: #f5c518; }
|
||||
.cartao.vermelho { background: #d92d20; }
|
||||
.jogo, .sumula { background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: .6rem .8rem; margin: 0 0 .6rem; }
|
||||
.jogo .quando { color: var(--muted); font-size: .85rem; }
|
||||
.jogo .confronto { display: grid; grid-template-columns: 1fr auto 1fr; gap: .5rem; align-items: center; font-weight: 700; }
|
||||
.jogo .confronto span:first-child { text-align: right; }
|
||||
.jogo .placar { background: var(--navy); color: #fff; border-radius: 6px; padding: .1rem .6rem; }
|
||||
.etiqueta { display: inline-block; font-size: .75rem; font-weight: 700; padding: .05rem .5rem; border-radius: 99px; background: #fff1e3; border: 1px solid var(--orange); margin-left: .4rem; }
|
||||
.sumula h3 { margin: 0 0 .3rem; font-size: 1rem; }
|
||||
.sumula ul { margin: .2rem 0 .5rem; padding-left: 1.2rem; }
|
||||
form { display: grid; gap: 1rem; }
|
||||
fieldset { border: 1px solid var(--line); border-radius: 8px; background: #fff; padding: .75rem 1rem 1rem; display: grid; gap: .6rem; }
|
||||
legend { font-weight: 700; padding: 0 .4rem; }
|
||||
input { font: inherit; padding: .5rem .6rem; border: 1px solid var(--line); border-radius: 6px; width: 100%; }
|
||||
.linha-ficha { display: grid; grid-template-columns: 1fr 5.5rem auto; gap: .4rem; align-items: center; }
|
||||
.linha-ficha button { padding: .4rem .6rem; }
|
||||
button { font: inherit; font-weight: 700; padding: .65rem 1.1rem; border: 0; border-radius: 8px; background: var(--orange); color: #fff; cursor: pointer; }
|
||||
button.secundario { background: #fff; color: var(--navy); border: 1px solid var(--line); justify-self: start; }
|
||||
button:disabled { opacity: .55; cursor: not-allowed; }
|
||||
.erro { color: #b42318; margin: 0; font-weight: 600; }
|
||||
.ok { background: #e7f6ec; border-left: 4px solid #067647; padding: .8rem 1rem; margin: 0; }
|
||||
#situacao.invalido { background: #fff1e3; border-left: 4px solid var(--orange); padding: 1rem 1.2rem; }
|
||||
.somente-leitura li { margin: .15rem 0; }
|
||||
@media (max-width: 30rem) { .linha-ficha { grid-template-columns: 1fr 4.5rem auto; } }
|
||||
|
||||
time.html: |
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<title>Seu time na competição — Athletic Map</title>
|
||||
<link rel="stylesheet" href="/publico/_estilo.css">
|
||||
<link rel="stylesheet" href="/publico/time.css?v=1">
|
||||
</head>
|
||||
<body>
|
||||
<header><div class="marca">Athletic <span>Map</span></div></header>
|
||||
<main>
|
||||
<h1 id="titulo">Seu time na competição</h1>
|
||||
<p id="situacao" role="status" aria-live="polite">Abrindo o link…</p>
|
||||
|
||||
<div id="painel" hidden>
|
||||
<div class="abas" role="tablist" aria-label="Seções">
|
||||
<button type="button" role="tab" id="aba-tabela" aria-controls="sec-tabela" aria-selected="true" data-aba="tabela">Tabela</button>
|
||||
<button type="button" role="tab" id="aba-jogos" aria-controls="sec-jogos" aria-selected="false" tabindex="-1" data-aba="jogos">Jogos</button>
|
||||
<button type="button" role="tab" id="aba-sumulas" aria-controls="sec-sumulas" aria-selected="false" tabindex="-1" data-aba="sumulas">Súmulas</button>
|
||||
<button type="button" role="tab" id="aba-ficha" aria-controls="sec-ficha" aria-selected="false" tabindex="-1" data-aba="ficha">Ficha do time</button>
|
||||
</div>
|
||||
|
||||
<section id="sec-tabela" role="tabpanel" aria-labelledby="aba-tabela">
|
||||
<h2>Tabela</h2>
|
||||
<div class="rolagem" id="tabela"></div>
|
||||
<p class="legenda">P pontos · J jogos · V vitórias · E empates · D derrotas · GP gols pró · GC gols contra · SG saldo de gols · % aproveitamento · CA cartões amarelos · CV cartões vermelhos</p>
|
||||
</section>
|
||||
|
||||
<section id="sec-jogos" role="tabpanel" aria-labelledby="aba-jogos" hidden>
|
||||
<h2>Jogos</h2>
|
||||
<div id="jogos"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-sumulas" role="tabpanel" aria-labelledby="aba-sumulas" hidden>
|
||||
<h2>Súmulas</h2>
|
||||
<div id="sumulas"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-ficha" role="tabpanel" aria-labelledby="aba-ficha" hidden>
|
||||
<h2>Ficha do time</h2>
|
||||
<p id="ficha-aviso" class="explica"></p>
|
||||
<form id="ficha" novalidate>
|
||||
<fieldset>
|
||||
<legend>Jogadores</legend>
|
||||
<div id="jogadores"></div>
|
||||
<button type="button" id="maisJogador" class="secundario">+ Jogador</button>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Comissão técnica</legend>
|
||||
<div id="comissao"></div>
|
||||
<datalist id="funcoes-comissao">
|
||||
<option value="Técnico"></option><option value="Auxiliar técnico"></option><option value="Preparador físico"></option>
|
||||
<option value="Preparador de goleiros"></option><option value="Médico"></option><option value="Fisioterapeuta"></option>
|
||||
<option value="Massagista"></option><option value="Roupeiro"></option><option value="Dirigente"></option>
|
||||
</datalist>
|
||||
<button type="button" id="maisComissao" class="secundario">+ Pessoa na comissão</button>
|
||||
</fieldset>
|
||||
<p id="ficha-erro" class="erro" role="alert" hidden></p>
|
||||
<p id="ficha-ok" class="ok" role="status" hidden></p>
|
||||
<button type="submit" id="salvar">Salvar ficha</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<script src="/publico/time.js?v=1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
time.js: |
|
||||
/* ===========================================================================
|
||||
* Link do time — a página que UMA pessoa do time abre pelo link com chave.
|
||||
*
|
||||
* Sem login: a chave no endereço é o único segredo. A página mostra a tabela,
|
||||
* os jogos e as súmulas da competição e a ficha do time; se o time for de
|
||||
* fora e ainda não houver jogo encerrado, a ficha pode ser corrigida aqui.
|
||||
*
|
||||
* Tudo o que vem do servidor entra na página com `textContent` — nunca como
|
||||
* HTML. O script é externo porque a CSP é `script-src 'self'`.
|
||||
* =========================================================================== */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var TOKEN = /^[A-Za-z0-9_-]{20,64}$/;
|
||||
var MAX_JOGADORES = 60;
|
||||
var MAX_COMISSAO = 15;
|
||||
var ABAS = ["tabela", "jogos", "sumulas", "ficha"];
|
||||
var ROTULOS_FORMA = { V: "Vitória", E: "Empate", D: "Derrota" };
|
||||
var ROTULOS_STATUS = {
|
||||
AGENDADA: "Agendado", ADIADA: "Adiado", EM_ANDAMENTO: "Em andamento", ENCERRADA: "Encerrado", CANCELADA: "Cancelado"
|
||||
};
|
||||
|
||||
var token = decodeURIComponent(location.pathname.split("/").filter(Boolean).pop() || "");
|
||||
var base = "/api/public/competicoes/times/" + encodeURIComponent(token);
|
||||
|
||||
var titulo = document.getElementById("titulo");
|
||||
var situacao = document.getElementById("situacao");
|
||||
var painel = document.getElementById("painel");
|
||||
var listaJogadores = document.getElementById("jogadores");
|
||||
var listaComissao = document.getElementById("comissao");
|
||||
var formFicha = document.getElementById("ficha");
|
||||
var fichaErro = document.getElementById("ficha-erro");
|
||||
var fichaOk = document.getElementById("ficha-ok");
|
||||
var salvar = document.getElementById("salvar");
|
||||
var visao = null;
|
||||
|
||||
function el(tag, classe, texto) {
|
||||
var e = document.createElement(tag);
|
||||
if (classe) e.className = classe;
|
||||
if (texto !== undefined && texto !== null) e.textContent = String(texto);
|
||||
return e;
|
||||
}
|
||||
|
||||
function limpar(no) {
|
||||
while (no.firstChild) no.removeChild(no.firstChild);
|
||||
}
|
||||
|
||||
function recusar(texto) {
|
||||
situacao.textContent = texto;
|
||||
situacao.className = "invalido";
|
||||
painel.hidden = true;
|
||||
}
|
||||
|
||||
function lerResposta(r) {
|
||||
return r.json().catch(function () { return {}; }).then(function (corpo) { return { status: r.status, corpo: corpo }; });
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ abas
|
||||
|
||||
function abrirAba(nome, focar) {
|
||||
for (var i = 0; i < ABAS.length; i++) {
|
||||
var ativa = ABAS[i] === nome;
|
||||
var aba = document.getElementById("aba-" + ABAS[i]);
|
||||
aba.setAttribute("aria-selected", ativa ? "true" : "false");
|
||||
aba.tabIndex = ativa ? 0 : -1;
|
||||
document.getElementById("sec-" + ABAS[i]).hidden = !ativa;
|
||||
if (ativa && focar) aba.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function aoTeclarNaAba(ev) {
|
||||
var i = ABAS.indexOf(ev.currentTarget.getAttribute("data-aba"));
|
||||
if (ev.key === "ArrowRight") { abrirAba(ABAS[(i + 1) % ABAS.length], true); ev.preventDefault(); }
|
||||
else if (ev.key === "ArrowLeft") { abrirAba(ABAS[(i + ABAS.length - 1) % ABAS.length], true); ev.preventDefault(); }
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ tabela
|
||||
|
||||
function dataBr(iso) {
|
||||
if (!iso) return "Data a definir";
|
||||
var d = new Date(iso);
|
||||
if (isNaN(d.getTime())) return "Data a definir";
|
||||
try {
|
||||
return d.toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" });
|
||||
} catch (e) {
|
||||
return d.toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
function percentual(n) {
|
||||
return (Math.round((n || 0) * 10) / 10).toString().replace(".", ",") + "%";
|
||||
}
|
||||
|
||||
function montarTabela(linhas, meuNome) {
|
||||
var alvo = document.getElementById("tabela");
|
||||
limpar(alvo);
|
||||
if (!linhas || !linhas.length) { alvo.appendChild(el("p", "explica", "A tabela aparece quando houver times e jogos.")); return; }
|
||||
var t = el("table");
|
||||
t.appendChild(el("caption", "explica", "Classificação"));
|
||||
var cab = el("tr");
|
||||
var cols = [["#", "Posição"], ["Time", "Time"], ["P", "Pontos"], ["J", "Jogos"], ["V", "Vitórias"], ["E", "Empates"],
|
||||
["D", "Derrotas"], ["GP", "Gols pró"], ["GC", "Gols contra"], ["SG", "Saldo de gols"], ["%", "Aproveitamento"],
|
||||
["Últimos 5", "Últimos 5 jogos"], ["CA", "Cartões amarelos"], ["CV", "Cartões vermelhos"]];
|
||||
cols.forEach(function (c) {
|
||||
var th = el("th", null, c[0]);
|
||||
th.scope = "col";
|
||||
th.title = c[1];
|
||||
cab.appendChild(th);
|
||||
});
|
||||
var thead = el("thead");
|
||||
thead.appendChild(cab);
|
||||
t.appendChild(thead);
|
||||
var corpo = el("tbody");
|
||||
linhas.forEach(function (l) {
|
||||
var tr = el("tr", l.time === meuNome ? "meu" : "");
|
||||
[l.posicao, l.time, l.pontos, l.jogos, l.vitorias, l.empates, l.derrotas, l.golsPro, l.golsContra, l.saldoGols,
|
||||
percentual(l.aproveitamento)].forEach(function (v) { tr.appendChild(el("td", null, v)); });
|
||||
var forma = el("td");
|
||||
var caixa = el("span", "forma");
|
||||
(l.ultimos || []).forEach(function (r) {
|
||||
var p = el("span", "ponto " + r);
|
||||
p.title = ROTULOS_FORMA[r] || r;
|
||||
p.setAttribute("role", "img");
|
||||
p.setAttribute("aria-label", ROTULOS_FORMA[r] || r);
|
||||
caixa.appendChild(p);
|
||||
});
|
||||
forma.appendChild(caixa);
|
||||
tr.appendChild(forma);
|
||||
var am = el("td");
|
||||
am.appendChild(el("span", "cartao amarelo"));
|
||||
am.appendChild(document.createTextNode(String(l.amarelos || 0)));
|
||||
tr.appendChild(am);
|
||||
var vm = el("td");
|
||||
vm.appendChild(el("span", "cartao vermelho"));
|
||||
vm.appendChild(document.createTextNode(String(l.vermelhos || 0)));
|
||||
tr.appendChild(vm);
|
||||
corpo.appendChild(tr);
|
||||
});
|
||||
t.appendChild(corpo);
|
||||
alvo.appendChild(t);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ jogos
|
||||
|
||||
function montarJogos(jogos) {
|
||||
var alvo = document.getElementById("jogos");
|
||||
limpar(alvo);
|
||||
if (!jogos || !jogos.length) { alvo.appendChild(el("p", "explica", "Ainda não há jogos marcados.")); return; }
|
||||
jogos.forEach(function (j) {
|
||||
var card = el("div", "jogo");
|
||||
var quando = el("div", "quando", (j.rodada ? j.rodada + " · " : "") + dataBr(j.data));
|
||||
var etiqueta = el("span", "etiqueta", ROTULOS_STATUS[j.status] || j.status);
|
||||
quando.appendChild(etiqueta);
|
||||
if (j.wo) quando.appendChild(el("span", "etiqueta", "W.O."));
|
||||
card.appendChild(quando);
|
||||
var linha = el("div", "confronto");
|
||||
linha.appendChild(el("span", null, j.mandante));
|
||||
var temPlacar = j.placarMandante !== undefined && j.placarMandante !== null;
|
||||
linha.appendChild(el("span", temPlacar ? "placar" : "", temPlacar ? j.placarMandante + " x " + j.placarVisitante : "x"));
|
||||
linha.appendChild(el("span", null, j.visitante));
|
||||
card.appendChild(linha);
|
||||
if (j.penaltis) card.appendChild(el("div", "quando", "Pênaltis: " + j.penaltis.mandante + " x " + j.penaltis.visitante));
|
||||
if (j.motivo) card.appendChild(el("div", "quando", "Motivo: " + j.motivo));
|
||||
alvo.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ súmulas
|
||||
|
||||
function pessoa(p) {
|
||||
if (!p) return "jogador não identificado";
|
||||
if (p.nome && p.numeroCamisa !== undefined && p.numeroCamisa !== null) return p.nome + " (" + p.numeroCamisa + ")";
|
||||
if (p.nome) return p.nome;
|
||||
if (p.numeroCamisa !== undefined && p.numeroCamisa !== null) return "camisa " + p.numeroCamisa;
|
||||
return "jogador não identificado";
|
||||
}
|
||||
|
||||
function momento(e) {
|
||||
var m = e.minuto !== undefined && e.minuto !== null ? e.minuto + "'" : "";
|
||||
var t = e.tempo ? e.tempo + "º tempo" : "";
|
||||
return [t, m].filter(Boolean).join(" ") || "—";
|
||||
}
|
||||
|
||||
function secaoDaSumula(card, nome, itens, texto) {
|
||||
if (!itens || !itens.length) return;
|
||||
card.appendChild(el("strong", null, nome));
|
||||
var ul = el("ul");
|
||||
itens.forEach(function (i) { ul.appendChild(el("li", null, texto(i))); });
|
||||
card.appendChild(ul);
|
||||
}
|
||||
|
||||
function montarSumulas(sumulas, jogos) {
|
||||
var alvo = document.getElementById("sumulas");
|
||||
limpar(alvo);
|
||||
if (!sumulas || !sumulas.length) { alvo.appendChild(el("p", "explica", "As súmulas dos jogos do seu time aparecem aqui.")); return; }
|
||||
sumulas.forEach(function (s) {
|
||||
var j = (jogos || []).filter(function (x) { return x.ref === s.jogoRef; })[0];
|
||||
var card = el("div", "sumula");
|
||||
var cab = j ? j.mandante + " x " + j.visitante : "Jogo " + s.jogoRef;
|
||||
if (j && j.placarMandante !== undefined && j.placarMandante !== null) {
|
||||
cab = j.mandante + " " + j.placarMandante + " x " + j.placarVisitante + " " + j.visitante;
|
||||
}
|
||||
card.appendChild(el("h3", null, cab));
|
||||
if (j) card.appendChild(el("div", "quando", (j.rodada ? j.rodada + " · " : "") + dataBr(j.data)));
|
||||
secaoDaSumula(card, "Gols", s.gols, function (g) { return momento(g) + " · " + g.time + " · " + pessoa(g.jogador); });
|
||||
secaoDaSumula(card, "Cartões", s.cartoes, function (c) {
|
||||
return momento(c) + " · " + (c.tipo === "VERMELHO" ? "vermelho" : "amarelo") + " · " + c.time + " · " + pessoa(c.jogador);
|
||||
});
|
||||
secaoDaSumula(card, "Substituições", s.substituicoes, function (t) {
|
||||
return momento(t) + " · " + t.time + " · sai " + pessoa(t.sai) + ", entra " + pessoa(t.entra);
|
||||
});
|
||||
alvo.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ ficha
|
||||
|
||||
function linhaDeJogador(j) {
|
||||
var linha = el("div", "linha-ficha linha-jogador");
|
||||
if (j && j.id) linha.setAttribute("data-id", j.id);
|
||||
var nome = el("input");
|
||||
nome.placeholder = "Nome do jogador";
|
||||
nome.maxLength = 120;
|
||||
nome.value = (j && j.nome) || "";
|
||||
nome.setAttribute("aria-label", "Nome do jogador");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
var camisa = el("input");
|
||||
camisa.type = "number";
|
||||
camisa.min = "0";
|
||||
camisa.max = "999";
|
||||
camisa.placeholder = "nº";
|
||||
camisa.value = j && j.numeroCamisa !== undefined && j.numeroCamisa !== null ? j.numeroCamisa : "";
|
||||
camisa.setAttribute("aria-label", "Número da camisa");
|
||||
camisa.setAttribute("data-campo", "camisa");
|
||||
var tirar = el("button", "secundario", "×");
|
||||
tirar.type = "button";
|
||||
tirar.setAttribute("aria-label", "Remover jogador");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(camisa);
|
||||
linha.appendChild(tirar);
|
||||
listaJogadores.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
function linhaDeComissao(m) {
|
||||
var linha = el("div", "linha-ficha linha-comissao");
|
||||
if (m && m.id) linha.setAttribute("data-id", m.id);
|
||||
var nome = el("input");
|
||||
nome.placeholder = "Nome";
|
||||
nome.maxLength = 120;
|
||||
nome.value = (m && m.nome) || "";
|
||||
nome.setAttribute("aria-label", "Nome na comissão técnica");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
var papel = el("input");
|
||||
papel.placeholder = "Função";
|
||||
papel.maxLength = 40;
|
||||
papel.value = (m && m.funcao) || "";
|
||||
papel.setAttribute("list", "funcoes-comissao");
|
||||
papel.setAttribute("aria-label", "Função na comissão técnica");
|
||||
papel.setAttribute("data-campo", "funcao");
|
||||
var tirar = el("button", "secundario", "×");
|
||||
tirar.type = "button";
|
||||
tirar.setAttribute("aria-label", "Remover da comissão");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(papel);
|
||||
linha.appendChild(tirar);
|
||||
listaComissao.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
/** Time da escola ou ficha fechada: só leitura (listas simples). */
|
||||
function fichaSomenteLeitura(ficha) {
|
||||
formFicha.hidden = true;
|
||||
var alvo = document.getElementById("ficha-leitura");
|
||||
if (!alvo) {
|
||||
alvo = el("div", "somente-leitura");
|
||||
alvo.id = "ficha-leitura";
|
||||
formFicha.parentNode.appendChild(alvo);
|
||||
}
|
||||
limpar(alvo);
|
||||
alvo.appendChild(el("h3", null, "Jogadores"));
|
||||
var ul = el("ul");
|
||||
(ficha.jogadores || []).forEach(function (j) {
|
||||
ul.appendChild(el("li", null, pessoa({ nome: j.nome, numeroCamisa: j.numeroCamisa })));
|
||||
});
|
||||
if (!ul.childNodes.length) ul.appendChild(el("li", null, "Nenhum jogador na ficha."));
|
||||
alvo.appendChild(ul);
|
||||
if ((ficha.comissao || []).length) {
|
||||
alvo.appendChild(el("h3", null, "Comissão técnica"));
|
||||
var uc = el("ul");
|
||||
ficha.comissao.forEach(function (m) { uc.appendChild(el("li", null, m.nome + " — " + m.funcao)); });
|
||||
alvo.appendChild(uc);
|
||||
}
|
||||
}
|
||||
|
||||
function montarFicha(ficha) {
|
||||
var aviso = document.getElementById("ficha-aviso");
|
||||
limpar(listaJogadores);
|
||||
limpar(listaComissao);
|
||||
if (!ficha.editavel) {
|
||||
aviso.textContent = "Esta ficha está fechada para alterações aqui. Se algo estiver errado, fale com a organização.";
|
||||
fichaSomenteLeitura(ficha);
|
||||
return;
|
||||
}
|
||||
aviso.textContent = "Confira os jogadores (nome e número da camisa) e a comissão técnica. Dá para corrigir até o primeiro jogo ser encerrado.";
|
||||
formFicha.hidden = false;
|
||||
(ficha.jogadores || []).forEach(linhaDeJogador);
|
||||
if (!(ficha.jogadores || []).length) for (var i = 0; i < 3; i++) linhaDeJogador();
|
||||
(ficha.comissao || []).forEach(linhaDeComissao);
|
||||
}
|
||||
|
||||
function mostrarErroFicha(texto) {
|
||||
fichaErro.textContent = texto || "";
|
||||
fichaErro.hidden = !texto;
|
||||
}
|
||||
|
||||
function mostrarOkFicha(texto) {
|
||||
fichaOk.textContent = texto || "";
|
||||
fichaOk.hidden = !texto;
|
||||
}
|
||||
|
||||
function corpoDaFicha() {
|
||||
var jogadores = [];
|
||||
var linhas = listaJogadores.querySelectorAll(".linha-jogador");
|
||||
for (var i = 0; i < linhas.length; i++) {
|
||||
var nome = linhas[i].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!nome) continue;
|
||||
var c = linhas[i].querySelector('[data-campo="camisa"]').value;
|
||||
var j = { nome: nome, numeroCamisa: c === "" ? null : parseInt(c, 10) };
|
||||
if (linhas[i].getAttribute("data-id")) j.id = linhas[i].getAttribute("data-id");
|
||||
jogadores.push(j);
|
||||
}
|
||||
var comissao = [];
|
||||
var faltaFuncao = false;
|
||||
var pessoas = listaComissao.querySelectorAll(".linha-comissao");
|
||||
for (var k = 0; k < pessoas.length; k++) {
|
||||
var n = pessoas[k].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!n) continue;
|
||||
var f = pessoas[k].querySelector('[data-campo="funcao"]').value.trim();
|
||||
if (!f) faltaFuncao = true;
|
||||
var m = { nome: n, funcao: f };
|
||||
if (pessoas[k].getAttribute("data-id")) m.id = pessoas[k].getAttribute("data-id");
|
||||
comissao.push(m);
|
||||
}
|
||||
return { jogadores: jogadores, comissao: comissao, faltaFuncao: faltaFuncao };
|
||||
}
|
||||
|
||||
function aoSalvar(ev) {
|
||||
ev.preventDefault();
|
||||
mostrarErroFicha("");
|
||||
mostrarOkFicha("");
|
||||
var c = corpoDaFicha();
|
||||
if (c.jogadores.length > MAX_JOGADORES) { mostrarErroFicha("No máximo " + MAX_JOGADORES + " jogadores."); return; }
|
||||
if (c.faltaFuncao) { mostrarErroFicha("Informe a função de cada pessoa da comissão técnica."); return; }
|
||||
if (c.comissao.length > MAX_COMISSAO) { mostrarErroFicha("No máximo " + MAX_COMISSAO + " pessoas na comissão técnica."); return; }
|
||||
salvar.disabled = true;
|
||||
salvar.setAttribute("aria-busy", "true");
|
||||
fetch(base + "/ficha", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ jogadores: c.jogadores, comissao: c.comissao })
|
||||
}).then(lerResposta).then(function (r) {
|
||||
salvar.disabled = false;
|
||||
salvar.removeAttribute("aria-busy");
|
||||
if (r.status >= 200 && r.status < 300) {
|
||||
mostrarOkFicha("Ficha salva.");
|
||||
return carregar(true);
|
||||
}
|
||||
if (r.status === 404) { recusar("Este link não está mais disponível."); return null; }
|
||||
mostrarErroFicha((r.corpo && r.corpo.detail) || "Não foi possível salvar agora. Tente de novo em instantes.");
|
||||
return null;
|
||||
}).catch(function () {
|
||||
salvar.disabled = false;
|
||||
salvar.removeAttribute("aria-busy");
|
||||
mostrarErroFicha("Sem conexão. Confira a internet e tente de novo.");
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ carga
|
||||
|
||||
function mostrar(v, manterAba) {
|
||||
visao = v;
|
||||
var nomeTime = (v.time && v.time.nome) || "Seu time";
|
||||
titulo.textContent = (v.competicao || "Competição") + " — " + nomeTime;
|
||||
document.title = titulo.textContent + " — Athletic Map";
|
||||
var partes = [];
|
||||
if (v.time && v.time.escola) partes.push(v.time.escola);
|
||||
if (v.time && v.time.cidade) partes.push(v.time.cidade);
|
||||
if (v.temporada && v.temporada.nome) partes.push("Temporada " + v.temporada.nome);
|
||||
if (v.time && v.time.status === "DESQUALIFICADO") partes.push("Time desqualificado");
|
||||
situacao.textContent = partes.join(" · ");
|
||||
situacao.className = "";
|
||||
painel.hidden = false;
|
||||
montarTabela(v.classificacao, nomeTime);
|
||||
montarJogos(v.jogos);
|
||||
montarSumulas(v.sumulas, v.jogos);
|
||||
montarFicha(v.ficha || { jogadores: [], comissao: [], editavel: false });
|
||||
if (!manterAba) abrirAba("tabela", false);
|
||||
}
|
||||
|
||||
function carregar(manterAba) {
|
||||
return fetch(base).then(lerResposta).then(function (r) {
|
||||
if (r.status === 404) { recusar("Este link não está disponível ou foi desativado. Peça um novo à organização."); return; }
|
||||
if (r.status === 429) { recusar("Muitas consultas seguidas. Aguarde alguns minutos e abra o link de novo."); return; }
|
||||
if (r.status !== 200) { recusar("Não foi possível abrir agora. Tente de novo em instantes."); return; }
|
||||
mostrar(r.corpo || {}, manterAba);
|
||||
}).catch(function () {
|
||||
recusar("Não foi possível abrir agora. Tente de novo em instantes.");
|
||||
});
|
||||
}
|
||||
|
||||
function iniciar() {
|
||||
if (!TOKEN.test(token)) { recusar("Este endereço não é válido."); return; }
|
||||
ABAS.forEach(function (nome) {
|
||||
var aba = document.getElementById("aba-" + nome);
|
||||
aba.addEventListener("click", function () { abrirAba(nome, false); });
|
||||
aba.addEventListener("keydown", aoTeclarNaAba);
|
||||
});
|
||||
document.getElementById("maisJogador").addEventListener("click", function () {
|
||||
if (listaJogadores.querySelectorAll(".linha-jogador").length < MAX_JOGADORES) linhaDeJogador().querySelector("input").focus();
|
||||
});
|
||||
document.getElementById("maisComissao").addEventListener("click", function () {
|
||||
if (listaComissao.querySelectorAll(".linha-comissao").length < MAX_COMISSAO) linhaDeComissao(null).querySelector("input").focus();
|
||||
});
|
||||
formFicha.addEventListener("submit", aoSalvar);
|
||||
carregar(false);
|
||||
}
|
||||
|
||||
iniciar();
|
||||
})();
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
|
||||
@@ -153,6 +153,9 @@ spec:
|
||||
- { key: "preparando.js", path: "preparando.js" }
|
||||
- { key: "privacidade.html", path: "privacidade.html" }
|
||||
- { key: "termos.html", path: "termos.html" }
|
||||
- { key: "time.css", path: "time.css" }
|
||||
- { key: "time.html", path: "time.html" }
|
||||
- { key: "time.js", path: "time.js" }
|
||||
- configMap:
|
||||
name: frontend-publico-binario
|
||||
items:
|
||||
|
||||
@@ -409,7 +409,7 @@ data:
|
||||
|
||||
<form id="formulario" hidden novalidate>
|
||||
<p class="explica">
|
||||
Preencha os dados do seu time e dos jogadores (só o nome e o número da camisa).
|
||||
Preencha os dados do seu time, dos jogadores (só o nome e o número da camisa) e da comissão técnica (nome e função).
|
||||
A organização confere e aprova antes do time entrar na competição.
|
||||
</p>
|
||||
|
||||
@@ -433,12 +433,23 @@ data:
|
||||
<button type="button" id="maisJogador" class="secundario">+ Jogador</button>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Comissão técnica</legend>
|
||||
<div id="comissao"></div>
|
||||
<datalist id="funcoes-comissao">
|
||||
<option value="Técnico"></option><option value="Auxiliar técnico"></option><option value="Preparador físico"></option>
|
||||
<option value="Preparador de goleiros"></option><option value="Médico"></option><option value="Fisioterapeuta"></option>
|
||||
<option value="Massagista"></option><option value="Roupeiro"></option><option value="Dirigente"></option>
|
||||
</datalist>
|
||||
<button type="button" id="maisComissao" class="secundario">+ Pessoa na comissão</button>
|
||||
</fieldset>
|
||||
|
||||
<p id="erro" class="erro" role="alert" hidden></p>
|
||||
<button type="submit" id="enviar">Enviar time</button>
|
||||
</form>
|
||||
</main>
|
||||
<script src="/publico/desafio.js?v=1"></script>
|
||||
<script src="/publico/convite.js?v=2"></script>
|
||||
<script src="/publico/convite.js?v=3"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -465,6 +476,7 @@ data:
|
||||
|
||||
var TOKEN = /^[A-Za-z0-9_-]{20,64}$/;
|
||||
var MAX_JOGADORES = 60;
|
||||
var MAX_COMISSAO = 15;
|
||||
|
||||
var token = decodeURIComponent((location.pathname.split("/").filter(Boolean).pop() || ""));
|
||||
var base = "/api/public/competicoes/convites/" + encodeURIComponent(token);
|
||||
@@ -473,6 +485,7 @@ data:
|
||||
var situacao = document.getElementById("situacao");
|
||||
var formulario = document.getElementById("formulario");
|
||||
var listaJogadores = document.getElementById("jogadores");
|
||||
var listaComissao = document.getElementById("comissao");
|
||||
var erro = document.getElementById("erro");
|
||||
var enviar = document.getElementById("enviar");
|
||||
var ROTULO_ENVIAR = enviar.textContent;
|
||||
@@ -520,6 +533,39 @@ data:
|
||||
return linha;
|
||||
}
|
||||
|
||||
/** Comissao tecnica (21/10/2026): nome e funcao; a funcao tem sugestoes, mas aceita texto livre. */
|
||||
function linhaDeComissao(funcao) {
|
||||
var linha = document.createElement("div");
|
||||
linha.className = "linha-jogador linha-comissao";
|
||||
|
||||
var nome = document.createElement("input");
|
||||
nome.placeholder = "Nome";
|
||||
nome.maxLength = 120;
|
||||
nome.setAttribute("aria-label", "Nome na comissão técnica");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
|
||||
var papel = document.createElement("input");
|
||||
papel.placeholder = "Função";
|
||||
papel.maxLength = 40;
|
||||
papel.value = funcao || "";
|
||||
papel.setAttribute("list", "funcoes-comissao");
|
||||
papel.setAttribute("aria-label", "Função na comissão técnica");
|
||||
papel.setAttribute("data-campo", "funcao");
|
||||
|
||||
var tirar = document.createElement("button");
|
||||
tirar.type = "button";
|
||||
tirar.className = "secundario";
|
||||
tirar.textContent = "×";
|
||||
tirar.setAttribute("aria-label", "Remover da comissão");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(papel);
|
||||
linha.appendChild(tirar);
|
||||
listaComissao.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
function valor(id) {
|
||||
var v = document.getElementById(id).value.trim();
|
||||
return v === "" ? null : v;
|
||||
@@ -534,14 +580,26 @@ data:
|
||||
var c = linhas[i].querySelector('[data-campo="camisa"]').value;
|
||||
jogadores.push({ nome: nome, numeroCamisa: c === "" ? null : parseInt(c, 10) });
|
||||
}
|
||||
var comissao = [];
|
||||
var faltaFuncao = false;
|
||||
var pessoas = listaComissao.querySelectorAll(".linha-comissao");
|
||||
for (var k = 0; k < pessoas.length; k++) {
|
||||
var n = pessoas[k].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!n) continue;
|
||||
var f = pessoas[k].querySelector('[data-campo="funcao"]').value.trim();
|
||||
if (!f) faltaFuncao = true;
|
||||
comissao.push({ nome: n, funcao: f });
|
||||
}
|
||||
return {
|
||||
faltaFuncao: faltaFuncao,
|
||||
nome: valor("nome"),
|
||||
escola: valor("escola"),
|
||||
cidade: valor("cidade"),
|
||||
contatoNome: valor("contatoNome"),
|
||||
contatoTelefone: valor("contatoTelefone"),
|
||||
contatoEmail: valor("contatoEmail"),
|
||||
jogadores: jogadores
|
||||
jogadores: jogadores,
|
||||
comissao: comissao
|
||||
};
|
||||
}
|
||||
|
||||
@@ -555,6 +613,9 @@ data:
|
||||
var corpo = montarCorpo();
|
||||
if (!corpo.nome) { mostrarErro("Informe o nome do time."); return; }
|
||||
if (corpo.jogadores.length > MAX_JOGADORES) { mostrarErro("No máximo " + MAX_JOGADORES + " jogadores."); return; }
|
||||
if (corpo.faltaFuncao) { mostrarErro("Informe a função de cada pessoa da comissão técnica."); return; }
|
||||
if (corpo.comissao.length > MAX_COMISSAO) { mostrarErro("No máximo " + MAX_COMISSAO + " pessoas na comissão técnica."); return; }
|
||||
delete corpo.faltaFuncao;
|
||||
enviar.disabled = true;
|
||||
enviar.textContent = "Verificando…";
|
||||
enviar.setAttribute("aria-busy", "true");
|
||||
@@ -615,6 +676,7 @@ data:
|
||||
situacao.className = "";
|
||||
formulario.hidden = false;
|
||||
for (var i = 0; i < 5; i++) linhaDeJogador();
|
||||
linhaDeComissao("Técnico");
|
||||
}).catch(function () {
|
||||
recusar("Não foi possível abrir o convite agora. Tente de novo em instantes.");
|
||||
});
|
||||
@@ -623,6 +685,9 @@ data:
|
||||
document.getElementById("maisJogador").addEventListener("click", function () {
|
||||
if (listaJogadores.querySelectorAll(".linha-jogador").length < MAX_JOGADORES) linhaDeJogador().querySelector("input").focus();
|
||||
});
|
||||
document.getElementById("maisComissao").addEventListener("click", function () {
|
||||
if (listaComissao.querySelectorAll(".linha-comissao").length < MAX_COMISSAO) linhaDeComissao("").querySelector("input").focus();
|
||||
});
|
||||
formulario.addEventListener("submit", aoEnviar);
|
||||
iniciar();
|
||||
})();
|
||||
@@ -8242,6 +8307,557 @@ data:
|
||||
</body>
|
||||
</html>
|
||||
|
||||
time.css: |
|
||||
/* Link do time: página pública, sem fonte nem script de fora (a CSP só deixa 'self'). Mobile primeiro. */
|
||||
main { max-width: 56rem; }
|
||||
.explica, .legenda { color: var(--muted); font-size: .9rem; }
|
||||
.abas { display: flex; gap: .25rem; overflow-x: auto; border-bottom: 2px solid var(--line); margin: 0 0 1rem; }
|
||||
.abas button { background: transparent; color: var(--navy); border: 0; border-bottom: 3px solid transparent; border-radius: 0; padding: .6rem .9rem; white-space: nowrap; font: inherit; font-weight: 700; cursor: pointer; }
|
||||
.abas button[aria-selected="true"] { border-bottom-color: var(--orange); }
|
||||
.abas button:focus-visible, button:focus-visible, input:focus-visible { outline: 3px solid var(--orange); outline-offset: 1px; }
|
||||
h2 { font-size: 1.15rem; margin: .5rem 0; }
|
||||
.rolagem { overflow-x: auto; }
|
||||
table { border-collapse: collapse; width: 100%; font-size: .9rem; background: #fff; }
|
||||
th, td { padding: .4rem .45rem; text-align: center; border-bottom: 1px solid var(--line); white-space: nowrap; }
|
||||
th:nth-child(2), td:nth-child(2) { text-align: left; white-space: normal; min-width: 8rem; }
|
||||
tr.meu { background: #fff4e5; font-weight: 700; }
|
||||
.forma { display: inline-flex; gap: 3px; vertical-align: middle; }
|
||||
.ponto { width: .8rem; height: .8rem; border-radius: 50%; display: inline-block; }
|
||||
.ponto.V { background: #067647; }
|
||||
.ponto.E { background: #98a2b3; }
|
||||
.ponto.D { background: #b42318; }
|
||||
.cartao { display: inline-block; width: .7rem; height: .95rem; border-radius: 2px; vertical-align: middle; margin-right: 3px; }
|
||||
.cartao.amarelo { background: #f5c518; }
|
||||
.cartao.vermelho { background: #d92d20; }
|
||||
.jogo, .sumula { background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: .6rem .8rem; margin: 0 0 .6rem; }
|
||||
.jogo .quando { color: var(--muted); font-size: .85rem; }
|
||||
.jogo .confronto { display: grid; grid-template-columns: 1fr auto 1fr; gap: .5rem; align-items: center; font-weight: 700; }
|
||||
.jogo .confronto span:first-child { text-align: right; }
|
||||
.jogo .placar { background: var(--navy); color: #fff; border-radius: 6px; padding: .1rem .6rem; }
|
||||
.etiqueta { display: inline-block; font-size: .75rem; font-weight: 700; padding: .05rem .5rem; border-radius: 99px; background: #fff1e3; border: 1px solid var(--orange); margin-left: .4rem; }
|
||||
.sumula h3 { margin: 0 0 .3rem; font-size: 1rem; }
|
||||
.sumula ul { margin: .2rem 0 .5rem; padding-left: 1.2rem; }
|
||||
form { display: grid; gap: 1rem; }
|
||||
fieldset { border: 1px solid var(--line); border-radius: 8px; background: #fff; padding: .75rem 1rem 1rem; display: grid; gap: .6rem; }
|
||||
legend { font-weight: 700; padding: 0 .4rem; }
|
||||
input { font: inherit; padding: .5rem .6rem; border: 1px solid var(--line); border-radius: 6px; width: 100%; }
|
||||
.linha-ficha { display: grid; grid-template-columns: 1fr 5.5rem auto; gap: .4rem; align-items: center; }
|
||||
.linha-ficha button { padding: .4rem .6rem; }
|
||||
button { font: inherit; font-weight: 700; padding: .65rem 1.1rem; border: 0; border-radius: 8px; background: var(--orange); color: #fff; cursor: pointer; }
|
||||
button.secundario { background: #fff; color: var(--navy); border: 1px solid var(--line); justify-self: start; }
|
||||
button:disabled { opacity: .55; cursor: not-allowed; }
|
||||
.erro { color: #b42318; margin: 0; font-weight: 600; }
|
||||
.ok { background: #e7f6ec; border-left: 4px solid #067647; padding: .8rem 1rem; margin: 0; }
|
||||
#situacao.invalido { background: #fff1e3; border-left: 4px solid var(--orange); padding: 1rem 1.2rem; }
|
||||
.somente-leitura li { margin: .15rem 0; }
|
||||
@media (max-width: 30rem) { .linha-ficha { grid-template-columns: 1fr 4.5rem auto; } }
|
||||
|
||||
time.html: |
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<title>Seu time na competição — Athletic Map</title>
|
||||
<link rel="stylesheet" href="/publico/_estilo.css">
|
||||
<link rel="stylesheet" href="/publico/time.css?v=1">
|
||||
</head>
|
||||
<body>
|
||||
<header><div class="marca">Athletic <span>Map</span></div></header>
|
||||
<main>
|
||||
<h1 id="titulo">Seu time na competição</h1>
|
||||
<p id="situacao" role="status" aria-live="polite">Abrindo o link…</p>
|
||||
|
||||
<div id="painel" hidden>
|
||||
<div class="abas" role="tablist" aria-label="Seções">
|
||||
<button type="button" role="tab" id="aba-tabela" aria-controls="sec-tabela" aria-selected="true" data-aba="tabela">Tabela</button>
|
||||
<button type="button" role="tab" id="aba-jogos" aria-controls="sec-jogos" aria-selected="false" tabindex="-1" data-aba="jogos">Jogos</button>
|
||||
<button type="button" role="tab" id="aba-sumulas" aria-controls="sec-sumulas" aria-selected="false" tabindex="-1" data-aba="sumulas">Súmulas</button>
|
||||
<button type="button" role="tab" id="aba-ficha" aria-controls="sec-ficha" aria-selected="false" tabindex="-1" data-aba="ficha">Ficha do time</button>
|
||||
</div>
|
||||
|
||||
<section id="sec-tabela" role="tabpanel" aria-labelledby="aba-tabela">
|
||||
<h2>Tabela</h2>
|
||||
<div class="rolagem" id="tabela"></div>
|
||||
<p class="legenda">P pontos · J jogos · V vitórias · E empates · D derrotas · GP gols pró · GC gols contra · SG saldo de gols · % aproveitamento · CA cartões amarelos · CV cartões vermelhos</p>
|
||||
</section>
|
||||
|
||||
<section id="sec-jogos" role="tabpanel" aria-labelledby="aba-jogos" hidden>
|
||||
<h2>Jogos</h2>
|
||||
<div id="jogos"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-sumulas" role="tabpanel" aria-labelledby="aba-sumulas" hidden>
|
||||
<h2>Súmulas</h2>
|
||||
<div id="sumulas"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-ficha" role="tabpanel" aria-labelledby="aba-ficha" hidden>
|
||||
<h2>Ficha do time</h2>
|
||||
<p id="ficha-aviso" class="explica"></p>
|
||||
<form id="ficha" novalidate>
|
||||
<fieldset>
|
||||
<legend>Jogadores</legend>
|
||||
<div id="jogadores"></div>
|
||||
<button type="button" id="maisJogador" class="secundario">+ Jogador</button>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Comissão técnica</legend>
|
||||
<div id="comissao"></div>
|
||||
<datalist id="funcoes-comissao">
|
||||
<option value="Técnico"></option><option value="Auxiliar técnico"></option><option value="Preparador físico"></option>
|
||||
<option value="Preparador de goleiros"></option><option value="Médico"></option><option value="Fisioterapeuta"></option>
|
||||
<option value="Massagista"></option><option value="Roupeiro"></option><option value="Dirigente"></option>
|
||||
</datalist>
|
||||
<button type="button" id="maisComissao" class="secundario">+ Pessoa na comissão</button>
|
||||
</fieldset>
|
||||
<p id="ficha-erro" class="erro" role="alert" hidden></p>
|
||||
<p id="ficha-ok" class="ok" role="status" hidden></p>
|
||||
<button type="submit" id="salvar">Salvar ficha</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<script src="/publico/time.js?v=1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
time.js: |
|
||||
/* ===========================================================================
|
||||
* Link do time — a página que UMA pessoa do time abre pelo link com chave.
|
||||
*
|
||||
* Sem login: a chave no endereço é o único segredo. A página mostra a tabela,
|
||||
* os jogos e as súmulas da competição e a ficha do time; se o time for de
|
||||
* fora e ainda não houver jogo encerrado, a ficha pode ser corrigida aqui.
|
||||
*
|
||||
* Tudo o que vem do servidor entra na página com `textContent` — nunca como
|
||||
* HTML. O script é externo porque a CSP é `script-src 'self'`.
|
||||
* =========================================================================== */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var TOKEN = /^[A-Za-z0-9_-]{20,64}$/;
|
||||
var MAX_JOGADORES = 60;
|
||||
var MAX_COMISSAO = 15;
|
||||
var ABAS = ["tabela", "jogos", "sumulas", "ficha"];
|
||||
var ROTULOS_FORMA = { V: "Vitória", E: "Empate", D: "Derrota" };
|
||||
var ROTULOS_STATUS = {
|
||||
AGENDADA: "Agendado", ADIADA: "Adiado", EM_ANDAMENTO: "Em andamento", ENCERRADA: "Encerrado", CANCELADA: "Cancelado"
|
||||
};
|
||||
|
||||
var token = decodeURIComponent(location.pathname.split("/").filter(Boolean).pop() || "");
|
||||
var base = "/api/public/competicoes/times/" + encodeURIComponent(token);
|
||||
|
||||
var titulo = document.getElementById("titulo");
|
||||
var situacao = document.getElementById("situacao");
|
||||
var painel = document.getElementById("painel");
|
||||
var listaJogadores = document.getElementById("jogadores");
|
||||
var listaComissao = document.getElementById("comissao");
|
||||
var formFicha = document.getElementById("ficha");
|
||||
var fichaErro = document.getElementById("ficha-erro");
|
||||
var fichaOk = document.getElementById("ficha-ok");
|
||||
var salvar = document.getElementById("salvar");
|
||||
var visao = null;
|
||||
|
||||
function el(tag, classe, texto) {
|
||||
var e = document.createElement(tag);
|
||||
if (classe) e.className = classe;
|
||||
if (texto !== undefined && texto !== null) e.textContent = String(texto);
|
||||
return e;
|
||||
}
|
||||
|
||||
function limpar(no) {
|
||||
while (no.firstChild) no.removeChild(no.firstChild);
|
||||
}
|
||||
|
||||
function recusar(texto) {
|
||||
situacao.textContent = texto;
|
||||
situacao.className = "invalido";
|
||||
painel.hidden = true;
|
||||
}
|
||||
|
||||
function lerResposta(r) {
|
||||
return r.json().catch(function () { return {}; }).then(function (corpo) { return { status: r.status, corpo: corpo }; });
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ abas
|
||||
|
||||
function abrirAba(nome, focar) {
|
||||
for (var i = 0; i < ABAS.length; i++) {
|
||||
var ativa = ABAS[i] === nome;
|
||||
var aba = document.getElementById("aba-" + ABAS[i]);
|
||||
aba.setAttribute("aria-selected", ativa ? "true" : "false");
|
||||
aba.tabIndex = ativa ? 0 : -1;
|
||||
document.getElementById("sec-" + ABAS[i]).hidden = !ativa;
|
||||
if (ativa && focar) aba.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function aoTeclarNaAba(ev) {
|
||||
var i = ABAS.indexOf(ev.currentTarget.getAttribute("data-aba"));
|
||||
if (ev.key === "ArrowRight") { abrirAba(ABAS[(i + 1) % ABAS.length], true); ev.preventDefault(); }
|
||||
else if (ev.key === "ArrowLeft") { abrirAba(ABAS[(i + ABAS.length - 1) % ABAS.length], true); ev.preventDefault(); }
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ tabela
|
||||
|
||||
function dataBr(iso) {
|
||||
if (!iso) return "Data a definir";
|
||||
var d = new Date(iso);
|
||||
if (isNaN(d.getTime())) return "Data a definir";
|
||||
try {
|
||||
return d.toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" });
|
||||
} catch (e) {
|
||||
return d.toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
function percentual(n) {
|
||||
return (Math.round((n || 0) * 10) / 10).toString().replace(".", ",") + "%";
|
||||
}
|
||||
|
||||
function montarTabela(linhas, meuNome) {
|
||||
var alvo = document.getElementById("tabela");
|
||||
limpar(alvo);
|
||||
if (!linhas || !linhas.length) { alvo.appendChild(el("p", "explica", "A tabela aparece quando houver times e jogos.")); return; }
|
||||
var t = el("table");
|
||||
t.appendChild(el("caption", "explica", "Classificação"));
|
||||
var cab = el("tr");
|
||||
var cols = [["#", "Posição"], ["Time", "Time"], ["P", "Pontos"], ["J", "Jogos"], ["V", "Vitórias"], ["E", "Empates"],
|
||||
["D", "Derrotas"], ["GP", "Gols pró"], ["GC", "Gols contra"], ["SG", "Saldo de gols"], ["%", "Aproveitamento"],
|
||||
["Últimos 5", "Últimos 5 jogos"], ["CA", "Cartões amarelos"], ["CV", "Cartões vermelhos"]];
|
||||
cols.forEach(function (c) {
|
||||
var th = el("th", null, c[0]);
|
||||
th.scope = "col";
|
||||
th.title = c[1];
|
||||
cab.appendChild(th);
|
||||
});
|
||||
var thead = el("thead");
|
||||
thead.appendChild(cab);
|
||||
t.appendChild(thead);
|
||||
var corpo = el("tbody");
|
||||
linhas.forEach(function (l) {
|
||||
var tr = el("tr", l.time === meuNome ? "meu" : "");
|
||||
[l.posicao, l.time, l.pontos, l.jogos, l.vitorias, l.empates, l.derrotas, l.golsPro, l.golsContra, l.saldoGols,
|
||||
percentual(l.aproveitamento)].forEach(function (v) { tr.appendChild(el("td", null, v)); });
|
||||
var forma = el("td");
|
||||
var caixa = el("span", "forma");
|
||||
(l.ultimos || []).forEach(function (r) {
|
||||
var p = el("span", "ponto " + r);
|
||||
p.title = ROTULOS_FORMA[r] || r;
|
||||
p.setAttribute("role", "img");
|
||||
p.setAttribute("aria-label", ROTULOS_FORMA[r] || r);
|
||||
caixa.appendChild(p);
|
||||
});
|
||||
forma.appendChild(caixa);
|
||||
tr.appendChild(forma);
|
||||
var am = el("td");
|
||||
am.appendChild(el("span", "cartao amarelo"));
|
||||
am.appendChild(document.createTextNode(String(l.amarelos || 0)));
|
||||
tr.appendChild(am);
|
||||
var vm = el("td");
|
||||
vm.appendChild(el("span", "cartao vermelho"));
|
||||
vm.appendChild(document.createTextNode(String(l.vermelhos || 0)));
|
||||
tr.appendChild(vm);
|
||||
corpo.appendChild(tr);
|
||||
});
|
||||
t.appendChild(corpo);
|
||||
alvo.appendChild(t);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ jogos
|
||||
|
||||
function montarJogos(jogos) {
|
||||
var alvo = document.getElementById("jogos");
|
||||
limpar(alvo);
|
||||
if (!jogos || !jogos.length) { alvo.appendChild(el("p", "explica", "Ainda não há jogos marcados.")); return; }
|
||||
jogos.forEach(function (j) {
|
||||
var card = el("div", "jogo");
|
||||
var quando = el("div", "quando", (j.rodada ? j.rodada + " · " : "") + dataBr(j.data));
|
||||
var etiqueta = el("span", "etiqueta", ROTULOS_STATUS[j.status] || j.status);
|
||||
quando.appendChild(etiqueta);
|
||||
if (j.wo) quando.appendChild(el("span", "etiqueta", "W.O."));
|
||||
card.appendChild(quando);
|
||||
var linha = el("div", "confronto");
|
||||
linha.appendChild(el("span", null, j.mandante));
|
||||
var temPlacar = j.placarMandante !== undefined && j.placarMandante !== null;
|
||||
linha.appendChild(el("span", temPlacar ? "placar" : "", temPlacar ? j.placarMandante + " x " + j.placarVisitante : "x"));
|
||||
linha.appendChild(el("span", null, j.visitante));
|
||||
card.appendChild(linha);
|
||||
if (j.penaltis) card.appendChild(el("div", "quando", "Pênaltis: " + j.penaltis.mandante + " x " + j.penaltis.visitante));
|
||||
if (j.motivo) card.appendChild(el("div", "quando", "Motivo: " + j.motivo));
|
||||
alvo.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ súmulas
|
||||
|
||||
function pessoa(p) {
|
||||
if (!p) return "jogador não identificado";
|
||||
if (p.nome && p.numeroCamisa !== undefined && p.numeroCamisa !== null) return p.nome + " (" + p.numeroCamisa + ")";
|
||||
if (p.nome) return p.nome;
|
||||
if (p.numeroCamisa !== undefined && p.numeroCamisa !== null) return "camisa " + p.numeroCamisa;
|
||||
return "jogador não identificado";
|
||||
}
|
||||
|
||||
function momento(e) {
|
||||
var m = e.minuto !== undefined && e.minuto !== null ? e.minuto + "'" : "";
|
||||
var t = e.tempo ? e.tempo + "º tempo" : "";
|
||||
return [t, m].filter(Boolean).join(" ") || "—";
|
||||
}
|
||||
|
||||
function secaoDaSumula(card, nome, itens, texto) {
|
||||
if (!itens || !itens.length) return;
|
||||
card.appendChild(el("strong", null, nome));
|
||||
var ul = el("ul");
|
||||
itens.forEach(function (i) { ul.appendChild(el("li", null, texto(i))); });
|
||||
card.appendChild(ul);
|
||||
}
|
||||
|
||||
function montarSumulas(sumulas, jogos) {
|
||||
var alvo = document.getElementById("sumulas");
|
||||
limpar(alvo);
|
||||
if (!sumulas || !sumulas.length) { alvo.appendChild(el("p", "explica", "As súmulas dos jogos do seu time aparecem aqui.")); return; }
|
||||
sumulas.forEach(function (s) {
|
||||
var j = (jogos || []).filter(function (x) { return x.ref === s.jogoRef; })[0];
|
||||
var card = el("div", "sumula");
|
||||
var cab = j ? j.mandante + " x " + j.visitante : "Jogo " + s.jogoRef;
|
||||
if (j && j.placarMandante !== undefined && j.placarMandante !== null) {
|
||||
cab = j.mandante + " " + j.placarMandante + " x " + j.placarVisitante + " " + j.visitante;
|
||||
}
|
||||
card.appendChild(el("h3", null, cab));
|
||||
if (j) card.appendChild(el("div", "quando", (j.rodada ? j.rodada + " · " : "") + dataBr(j.data)));
|
||||
secaoDaSumula(card, "Gols", s.gols, function (g) { return momento(g) + " · " + g.time + " · " + pessoa(g.jogador); });
|
||||
secaoDaSumula(card, "Cartões", s.cartoes, function (c) {
|
||||
return momento(c) + " · " + (c.tipo === "VERMELHO" ? "vermelho" : "amarelo") + " · " + c.time + " · " + pessoa(c.jogador);
|
||||
});
|
||||
secaoDaSumula(card, "Substituições", s.substituicoes, function (t) {
|
||||
return momento(t) + " · " + t.time + " · sai " + pessoa(t.sai) + ", entra " + pessoa(t.entra);
|
||||
});
|
||||
alvo.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ ficha
|
||||
|
||||
function linhaDeJogador(j) {
|
||||
var linha = el("div", "linha-ficha linha-jogador");
|
||||
if (j && j.id) linha.setAttribute("data-id", j.id);
|
||||
var nome = el("input");
|
||||
nome.placeholder = "Nome do jogador";
|
||||
nome.maxLength = 120;
|
||||
nome.value = (j && j.nome) || "";
|
||||
nome.setAttribute("aria-label", "Nome do jogador");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
var camisa = el("input");
|
||||
camisa.type = "number";
|
||||
camisa.min = "0";
|
||||
camisa.max = "999";
|
||||
camisa.placeholder = "nº";
|
||||
camisa.value = j && j.numeroCamisa !== undefined && j.numeroCamisa !== null ? j.numeroCamisa : "";
|
||||
camisa.setAttribute("aria-label", "Número da camisa");
|
||||
camisa.setAttribute("data-campo", "camisa");
|
||||
var tirar = el("button", "secundario", "×");
|
||||
tirar.type = "button";
|
||||
tirar.setAttribute("aria-label", "Remover jogador");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(camisa);
|
||||
linha.appendChild(tirar);
|
||||
listaJogadores.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
function linhaDeComissao(m) {
|
||||
var linha = el("div", "linha-ficha linha-comissao");
|
||||
if (m && m.id) linha.setAttribute("data-id", m.id);
|
||||
var nome = el("input");
|
||||
nome.placeholder = "Nome";
|
||||
nome.maxLength = 120;
|
||||
nome.value = (m && m.nome) || "";
|
||||
nome.setAttribute("aria-label", "Nome na comissão técnica");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
var papel = el("input");
|
||||
papel.placeholder = "Função";
|
||||
papel.maxLength = 40;
|
||||
papel.value = (m && m.funcao) || "";
|
||||
papel.setAttribute("list", "funcoes-comissao");
|
||||
papel.setAttribute("aria-label", "Função na comissão técnica");
|
||||
papel.setAttribute("data-campo", "funcao");
|
||||
var tirar = el("button", "secundario", "×");
|
||||
tirar.type = "button";
|
||||
tirar.setAttribute("aria-label", "Remover da comissão");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(papel);
|
||||
linha.appendChild(tirar);
|
||||
listaComissao.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
/** Time da escola ou ficha fechada: só leitura (listas simples). */
|
||||
function fichaSomenteLeitura(ficha) {
|
||||
formFicha.hidden = true;
|
||||
var alvo = document.getElementById("ficha-leitura");
|
||||
if (!alvo) {
|
||||
alvo = el("div", "somente-leitura");
|
||||
alvo.id = "ficha-leitura";
|
||||
formFicha.parentNode.appendChild(alvo);
|
||||
}
|
||||
limpar(alvo);
|
||||
alvo.appendChild(el("h3", null, "Jogadores"));
|
||||
var ul = el("ul");
|
||||
(ficha.jogadores || []).forEach(function (j) {
|
||||
ul.appendChild(el("li", null, pessoa({ nome: j.nome, numeroCamisa: j.numeroCamisa })));
|
||||
});
|
||||
if (!ul.childNodes.length) ul.appendChild(el("li", null, "Nenhum jogador na ficha."));
|
||||
alvo.appendChild(ul);
|
||||
if ((ficha.comissao || []).length) {
|
||||
alvo.appendChild(el("h3", null, "Comissão técnica"));
|
||||
var uc = el("ul");
|
||||
ficha.comissao.forEach(function (m) { uc.appendChild(el("li", null, m.nome + " — " + m.funcao)); });
|
||||
alvo.appendChild(uc);
|
||||
}
|
||||
}
|
||||
|
||||
function montarFicha(ficha) {
|
||||
var aviso = document.getElementById("ficha-aviso");
|
||||
limpar(listaJogadores);
|
||||
limpar(listaComissao);
|
||||
if (!ficha.editavel) {
|
||||
aviso.textContent = "Esta ficha está fechada para alterações aqui. Se algo estiver errado, fale com a organização.";
|
||||
fichaSomenteLeitura(ficha);
|
||||
return;
|
||||
}
|
||||
aviso.textContent = "Confira os jogadores (nome e número da camisa) e a comissão técnica. Dá para corrigir até o primeiro jogo ser encerrado.";
|
||||
formFicha.hidden = false;
|
||||
(ficha.jogadores || []).forEach(linhaDeJogador);
|
||||
if (!(ficha.jogadores || []).length) for (var i = 0; i < 3; i++) linhaDeJogador();
|
||||
(ficha.comissao || []).forEach(linhaDeComissao);
|
||||
}
|
||||
|
||||
function mostrarErroFicha(texto) {
|
||||
fichaErro.textContent = texto || "";
|
||||
fichaErro.hidden = !texto;
|
||||
}
|
||||
|
||||
function mostrarOkFicha(texto) {
|
||||
fichaOk.textContent = texto || "";
|
||||
fichaOk.hidden = !texto;
|
||||
}
|
||||
|
||||
function corpoDaFicha() {
|
||||
var jogadores = [];
|
||||
var linhas = listaJogadores.querySelectorAll(".linha-jogador");
|
||||
for (var i = 0; i < linhas.length; i++) {
|
||||
var nome = linhas[i].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!nome) continue;
|
||||
var c = linhas[i].querySelector('[data-campo="camisa"]').value;
|
||||
var j = { nome: nome, numeroCamisa: c === "" ? null : parseInt(c, 10) };
|
||||
if (linhas[i].getAttribute("data-id")) j.id = linhas[i].getAttribute("data-id");
|
||||
jogadores.push(j);
|
||||
}
|
||||
var comissao = [];
|
||||
var faltaFuncao = false;
|
||||
var pessoas = listaComissao.querySelectorAll(".linha-comissao");
|
||||
for (var k = 0; k < pessoas.length; k++) {
|
||||
var n = pessoas[k].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!n) continue;
|
||||
var f = pessoas[k].querySelector('[data-campo="funcao"]').value.trim();
|
||||
if (!f) faltaFuncao = true;
|
||||
var m = { nome: n, funcao: f };
|
||||
if (pessoas[k].getAttribute("data-id")) m.id = pessoas[k].getAttribute("data-id");
|
||||
comissao.push(m);
|
||||
}
|
||||
return { jogadores: jogadores, comissao: comissao, faltaFuncao: faltaFuncao };
|
||||
}
|
||||
|
||||
function aoSalvar(ev) {
|
||||
ev.preventDefault();
|
||||
mostrarErroFicha("");
|
||||
mostrarOkFicha("");
|
||||
var c = corpoDaFicha();
|
||||
if (c.jogadores.length > MAX_JOGADORES) { mostrarErroFicha("No máximo " + MAX_JOGADORES + " jogadores."); return; }
|
||||
if (c.faltaFuncao) { mostrarErroFicha("Informe a função de cada pessoa da comissão técnica."); return; }
|
||||
if (c.comissao.length > MAX_COMISSAO) { mostrarErroFicha("No máximo " + MAX_COMISSAO + " pessoas na comissão técnica."); return; }
|
||||
salvar.disabled = true;
|
||||
salvar.setAttribute("aria-busy", "true");
|
||||
fetch(base + "/ficha", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ jogadores: c.jogadores, comissao: c.comissao })
|
||||
}).then(lerResposta).then(function (r) {
|
||||
salvar.disabled = false;
|
||||
salvar.removeAttribute("aria-busy");
|
||||
if (r.status >= 200 && r.status < 300) {
|
||||
mostrarOkFicha("Ficha salva.");
|
||||
return carregar(true);
|
||||
}
|
||||
if (r.status === 404) { recusar("Este link não está mais disponível."); return null; }
|
||||
mostrarErroFicha((r.corpo && r.corpo.detail) || "Não foi possível salvar agora. Tente de novo em instantes.");
|
||||
return null;
|
||||
}).catch(function () {
|
||||
salvar.disabled = false;
|
||||
salvar.removeAttribute("aria-busy");
|
||||
mostrarErroFicha("Sem conexão. Confira a internet e tente de novo.");
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ carga
|
||||
|
||||
function mostrar(v, manterAba) {
|
||||
visao = v;
|
||||
var nomeTime = (v.time && v.time.nome) || "Seu time";
|
||||
titulo.textContent = (v.competicao || "Competição") + " — " + nomeTime;
|
||||
document.title = titulo.textContent + " — Athletic Map";
|
||||
var partes = [];
|
||||
if (v.time && v.time.escola) partes.push(v.time.escola);
|
||||
if (v.time && v.time.cidade) partes.push(v.time.cidade);
|
||||
if (v.temporada && v.temporada.nome) partes.push("Temporada " + v.temporada.nome);
|
||||
if (v.time && v.time.status === "DESQUALIFICADO") partes.push("Time desqualificado");
|
||||
situacao.textContent = partes.join(" · ");
|
||||
situacao.className = "";
|
||||
painel.hidden = false;
|
||||
montarTabela(v.classificacao, nomeTime);
|
||||
montarJogos(v.jogos);
|
||||
montarSumulas(v.sumulas, v.jogos);
|
||||
montarFicha(v.ficha || { jogadores: [], comissao: [], editavel: false });
|
||||
if (!manterAba) abrirAba("tabela", false);
|
||||
}
|
||||
|
||||
function carregar(manterAba) {
|
||||
return fetch(base).then(lerResposta).then(function (r) {
|
||||
if (r.status === 404) { recusar("Este link não está disponível ou foi desativado. Peça um novo à organização."); return; }
|
||||
if (r.status === 429) { recusar("Muitas consultas seguidas. Aguarde alguns minutos e abra o link de novo."); return; }
|
||||
if (r.status !== 200) { recusar("Não foi possível abrir agora. Tente de novo em instantes."); return; }
|
||||
mostrar(r.corpo || {}, manterAba);
|
||||
}).catch(function () {
|
||||
recusar("Não foi possível abrir agora. Tente de novo em instantes.");
|
||||
});
|
||||
}
|
||||
|
||||
function iniciar() {
|
||||
if (!TOKEN.test(token)) { recusar("Este endereço não é válido."); return; }
|
||||
ABAS.forEach(function (nome) {
|
||||
var aba = document.getElementById("aba-" + nome);
|
||||
aba.addEventListener("click", function () { abrirAba(nome, false); });
|
||||
aba.addEventListener("keydown", aoTeclarNaAba);
|
||||
});
|
||||
document.getElementById("maisJogador").addEventListener("click", function () {
|
||||
if (listaJogadores.querySelectorAll(".linha-jogador").length < MAX_JOGADORES) linhaDeJogador().querySelector("input").focus();
|
||||
});
|
||||
document.getElementById("maisComissao").addEventListener("click", function () {
|
||||
if (listaComissao.querySelectorAll(".linha-comissao").length < MAX_COMISSAO) linhaDeComissao(null).querySelector("input").focus();
|
||||
});
|
||||
formFicha.addEventListener("submit", aoSalvar);
|
||||
carregar(false);
|
||||
}
|
||||
|
||||
iniciar();
|
||||
})();
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
|
||||
@@ -153,6 +153,9 @@ spec:
|
||||
- { key: "preparando.js", path: "preparando.js" }
|
||||
- { key: "privacidade.html", path: "privacidade.html" }
|
||||
- { key: "termos.html", path: "termos.html" }
|
||||
- { key: "time.css", path: "time.css" }
|
||||
- { key: "time.html", path: "time.html" }
|
||||
- { key: "time.js", path: "time.js" }
|
||||
- configMap:
|
||||
name: frontend-publico-binario
|
||||
items:
|
||||
|
||||
@@ -409,7 +409,7 @@ data:
|
||||
|
||||
<form id="formulario" hidden novalidate>
|
||||
<p class="explica">
|
||||
Preencha os dados do seu time e dos jogadores (só o nome e o número da camisa).
|
||||
Preencha os dados do seu time, dos jogadores (só o nome e o número da camisa) e da comissão técnica (nome e função).
|
||||
A organização confere e aprova antes do time entrar na competição.
|
||||
</p>
|
||||
|
||||
@@ -433,12 +433,23 @@ data:
|
||||
<button type="button" id="maisJogador" class="secundario">+ Jogador</button>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Comissão técnica</legend>
|
||||
<div id="comissao"></div>
|
||||
<datalist id="funcoes-comissao">
|
||||
<option value="Técnico"></option><option value="Auxiliar técnico"></option><option value="Preparador físico"></option>
|
||||
<option value="Preparador de goleiros"></option><option value="Médico"></option><option value="Fisioterapeuta"></option>
|
||||
<option value="Massagista"></option><option value="Roupeiro"></option><option value="Dirigente"></option>
|
||||
</datalist>
|
||||
<button type="button" id="maisComissao" class="secundario">+ Pessoa na comissão</button>
|
||||
</fieldset>
|
||||
|
||||
<p id="erro" class="erro" role="alert" hidden></p>
|
||||
<button type="submit" id="enviar">Enviar time</button>
|
||||
</form>
|
||||
</main>
|
||||
<script src="/publico/desafio.js?v=1"></script>
|
||||
<script src="/publico/convite.js?v=2"></script>
|
||||
<script src="/publico/convite.js?v=3"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -465,6 +476,7 @@ data:
|
||||
|
||||
var TOKEN = /^[A-Za-z0-9_-]{20,64}$/;
|
||||
var MAX_JOGADORES = 60;
|
||||
var MAX_COMISSAO = 15;
|
||||
|
||||
var token = decodeURIComponent((location.pathname.split("/").filter(Boolean).pop() || ""));
|
||||
var base = "/api/public/competicoes/convites/" + encodeURIComponent(token);
|
||||
@@ -473,6 +485,7 @@ data:
|
||||
var situacao = document.getElementById("situacao");
|
||||
var formulario = document.getElementById("formulario");
|
||||
var listaJogadores = document.getElementById("jogadores");
|
||||
var listaComissao = document.getElementById("comissao");
|
||||
var erro = document.getElementById("erro");
|
||||
var enviar = document.getElementById("enviar");
|
||||
var ROTULO_ENVIAR = enviar.textContent;
|
||||
@@ -520,6 +533,39 @@ data:
|
||||
return linha;
|
||||
}
|
||||
|
||||
/** Comissao tecnica (21/10/2026): nome e funcao; a funcao tem sugestoes, mas aceita texto livre. */
|
||||
function linhaDeComissao(funcao) {
|
||||
var linha = document.createElement("div");
|
||||
linha.className = "linha-jogador linha-comissao";
|
||||
|
||||
var nome = document.createElement("input");
|
||||
nome.placeholder = "Nome";
|
||||
nome.maxLength = 120;
|
||||
nome.setAttribute("aria-label", "Nome na comissão técnica");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
|
||||
var papel = document.createElement("input");
|
||||
papel.placeholder = "Função";
|
||||
papel.maxLength = 40;
|
||||
papel.value = funcao || "";
|
||||
papel.setAttribute("list", "funcoes-comissao");
|
||||
papel.setAttribute("aria-label", "Função na comissão técnica");
|
||||
papel.setAttribute("data-campo", "funcao");
|
||||
|
||||
var tirar = document.createElement("button");
|
||||
tirar.type = "button";
|
||||
tirar.className = "secundario";
|
||||
tirar.textContent = "×";
|
||||
tirar.setAttribute("aria-label", "Remover da comissão");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(papel);
|
||||
linha.appendChild(tirar);
|
||||
listaComissao.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
function valor(id) {
|
||||
var v = document.getElementById(id).value.trim();
|
||||
return v === "" ? null : v;
|
||||
@@ -534,14 +580,26 @@ data:
|
||||
var c = linhas[i].querySelector('[data-campo="camisa"]').value;
|
||||
jogadores.push({ nome: nome, numeroCamisa: c === "" ? null : parseInt(c, 10) });
|
||||
}
|
||||
var comissao = [];
|
||||
var faltaFuncao = false;
|
||||
var pessoas = listaComissao.querySelectorAll(".linha-comissao");
|
||||
for (var k = 0; k < pessoas.length; k++) {
|
||||
var n = pessoas[k].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!n) continue;
|
||||
var f = pessoas[k].querySelector('[data-campo="funcao"]').value.trim();
|
||||
if (!f) faltaFuncao = true;
|
||||
comissao.push({ nome: n, funcao: f });
|
||||
}
|
||||
return {
|
||||
faltaFuncao: faltaFuncao,
|
||||
nome: valor("nome"),
|
||||
escola: valor("escola"),
|
||||
cidade: valor("cidade"),
|
||||
contatoNome: valor("contatoNome"),
|
||||
contatoTelefone: valor("contatoTelefone"),
|
||||
contatoEmail: valor("contatoEmail"),
|
||||
jogadores: jogadores
|
||||
jogadores: jogadores,
|
||||
comissao: comissao
|
||||
};
|
||||
}
|
||||
|
||||
@@ -555,6 +613,9 @@ data:
|
||||
var corpo = montarCorpo();
|
||||
if (!corpo.nome) { mostrarErro("Informe o nome do time."); return; }
|
||||
if (corpo.jogadores.length > MAX_JOGADORES) { mostrarErro("No máximo " + MAX_JOGADORES + " jogadores."); return; }
|
||||
if (corpo.faltaFuncao) { mostrarErro("Informe a função de cada pessoa da comissão técnica."); return; }
|
||||
if (corpo.comissao.length > MAX_COMISSAO) { mostrarErro("No máximo " + MAX_COMISSAO + " pessoas na comissão técnica."); return; }
|
||||
delete corpo.faltaFuncao;
|
||||
enviar.disabled = true;
|
||||
enviar.textContent = "Verificando…";
|
||||
enviar.setAttribute("aria-busy", "true");
|
||||
@@ -615,6 +676,7 @@ data:
|
||||
situacao.className = "";
|
||||
formulario.hidden = false;
|
||||
for (var i = 0; i < 5; i++) linhaDeJogador();
|
||||
linhaDeComissao("Técnico");
|
||||
}).catch(function () {
|
||||
recusar("Não foi possível abrir o convite agora. Tente de novo em instantes.");
|
||||
});
|
||||
@@ -623,6 +685,9 @@ data:
|
||||
document.getElementById("maisJogador").addEventListener("click", function () {
|
||||
if (listaJogadores.querySelectorAll(".linha-jogador").length < MAX_JOGADORES) linhaDeJogador().querySelector("input").focus();
|
||||
});
|
||||
document.getElementById("maisComissao").addEventListener("click", function () {
|
||||
if (listaComissao.querySelectorAll(".linha-comissao").length < MAX_COMISSAO) linhaDeComissao("").querySelector("input").focus();
|
||||
});
|
||||
formulario.addEventListener("submit", aoEnviar);
|
||||
iniciar();
|
||||
})();
|
||||
@@ -8242,6 +8307,557 @@ data:
|
||||
</body>
|
||||
</html>
|
||||
|
||||
time.css: |
|
||||
/* Link do time: página pública, sem fonte nem script de fora (a CSP só deixa 'self'). Mobile primeiro. */
|
||||
main { max-width: 56rem; }
|
||||
.explica, .legenda { color: var(--muted); font-size: .9rem; }
|
||||
.abas { display: flex; gap: .25rem; overflow-x: auto; border-bottom: 2px solid var(--line); margin: 0 0 1rem; }
|
||||
.abas button { background: transparent; color: var(--navy); border: 0; border-bottom: 3px solid transparent; border-radius: 0; padding: .6rem .9rem; white-space: nowrap; font: inherit; font-weight: 700; cursor: pointer; }
|
||||
.abas button[aria-selected="true"] { border-bottom-color: var(--orange); }
|
||||
.abas button:focus-visible, button:focus-visible, input:focus-visible { outline: 3px solid var(--orange); outline-offset: 1px; }
|
||||
h2 { font-size: 1.15rem; margin: .5rem 0; }
|
||||
.rolagem { overflow-x: auto; }
|
||||
table { border-collapse: collapse; width: 100%; font-size: .9rem; background: #fff; }
|
||||
th, td { padding: .4rem .45rem; text-align: center; border-bottom: 1px solid var(--line); white-space: nowrap; }
|
||||
th:nth-child(2), td:nth-child(2) { text-align: left; white-space: normal; min-width: 8rem; }
|
||||
tr.meu { background: #fff4e5; font-weight: 700; }
|
||||
.forma { display: inline-flex; gap: 3px; vertical-align: middle; }
|
||||
.ponto { width: .8rem; height: .8rem; border-radius: 50%; display: inline-block; }
|
||||
.ponto.V { background: #067647; }
|
||||
.ponto.E { background: #98a2b3; }
|
||||
.ponto.D { background: #b42318; }
|
||||
.cartao { display: inline-block; width: .7rem; height: .95rem; border-radius: 2px; vertical-align: middle; margin-right: 3px; }
|
||||
.cartao.amarelo { background: #f5c518; }
|
||||
.cartao.vermelho { background: #d92d20; }
|
||||
.jogo, .sumula { background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: .6rem .8rem; margin: 0 0 .6rem; }
|
||||
.jogo .quando { color: var(--muted); font-size: .85rem; }
|
||||
.jogo .confronto { display: grid; grid-template-columns: 1fr auto 1fr; gap: .5rem; align-items: center; font-weight: 700; }
|
||||
.jogo .confronto span:first-child { text-align: right; }
|
||||
.jogo .placar { background: var(--navy); color: #fff; border-radius: 6px; padding: .1rem .6rem; }
|
||||
.etiqueta { display: inline-block; font-size: .75rem; font-weight: 700; padding: .05rem .5rem; border-radius: 99px; background: #fff1e3; border: 1px solid var(--orange); margin-left: .4rem; }
|
||||
.sumula h3 { margin: 0 0 .3rem; font-size: 1rem; }
|
||||
.sumula ul { margin: .2rem 0 .5rem; padding-left: 1.2rem; }
|
||||
form { display: grid; gap: 1rem; }
|
||||
fieldset { border: 1px solid var(--line); border-radius: 8px; background: #fff; padding: .75rem 1rem 1rem; display: grid; gap: .6rem; }
|
||||
legend { font-weight: 700; padding: 0 .4rem; }
|
||||
input { font: inherit; padding: .5rem .6rem; border: 1px solid var(--line); border-radius: 6px; width: 100%; }
|
||||
.linha-ficha { display: grid; grid-template-columns: 1fr 5.5rem auto; gap: .4rem; align-items: center; }
|
||||
.linha-ficha button { padding: .4rem .6rem; }
|
||||
button { font: inherit; font-weight: 700; padding: .65rem 1.1rem; border: 0; border-radius: 8px; background: var(--orange); color: #fff; cursor: pointer; }
|
||||
button.secundario { background: #fff; color: var(--navy); border: 1px solid var(--line); justify-self: start; }
|
||||
button:disabled { opacity: .55; cursor: not-allowed; }
|
||||
.erro { color: #b42318; margin: 0; font-weight: 600; }
|
||||
.ok { background: #e7f6ec; border-left: 4px solid #067647; padding: .8rem 1rem; margin: 0; }
|
||||
#situacao.invalido { background: #fff1e3; border-left: 4px solid var(--orange); padding: 1rem 1.2rem; }
|
||||
.somente-leitura li { margin: .15rem 0; }
|
||||
@media (max-width: 30rem) { .linha-ficha { grid-template-columns: 1fr 4.5rem auto; } }
|
||||
|
||||
time.html: |
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<title>Seu time na competição — Athletic Map</title>
|
||||
<link rel="stylesheet" href="/publico/_estilo.css">
|
||||
<link rel="stylesheet" href="/publico/time.css?v=1">
|
||||
</head>
|
||||
<body>
|
||||
<header><div class="marca">Athletic <span>Map</span></div></header>
|
||||
<main>
|
||||
<h1 id="titulo">Seu time na competição</h1>
|
||||
<p id="situacao" role="status" aria-live="polite">Abrindo o link…</p>
|
||||
|
||||
<div id="painel" hidden>
|
||||
<div class="abas" role="tablist" aria-label="Seções">
|
||||
<button type="button" role="tab" id="aba-tabela" aria-controls="sec-tabela" aria-selected="true" data-aba="tabela">Tabela</button>
|
||||
<button type="button" role="tab" id="aba-jogos" aria-controls="sec-jogos" aria-selected="false" tabindex="-1" data-aba="jogos">Jogos</button>
|
||||
<button type="button" role="tab" id="aba-sumulas" aria-controls="sec-sumulas" aria-selected="false" tabindex="-1" data-aba="sumulas">Súmulas</button>
|
||||
<button type="button" role="tab" id="aba-ficha" aria-controls="sec-ficha" aria-selected="false" tabindex="-1" data-aba="ficha">Ficha do time</button>
|
||||
</div>
|
||||
|
||||
<section id="sec-tabela" role="tabpanel" aria-labelledby="aba-tabela">
|
||||
<h2>Tabela</h2>
|
||||
<div class="rolagem" id="tabela"></div>
|
||||
<p class="legenda">P pontos · J jogos · V vitórias · E empates · D derrotas · GP gols pró · GC gols contra · SG saldo de gols · % aproveitamento · CA cartões amarelos · CV cartões vermelhos</p>
|
||||
</section>
|
||||
|
||||
<section id="sec-jogos" role="tabpanel" aria-labelledby="aba-jogos" hidden>
|
||||
<h2>Jogos</h2>
|
||||
<div id="jogos"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-sumulas" role="tabpanel" aria-labelledby="aba-sumulas" hidden>
|
||||
<h2>Súmulas</h2>
|
||||
<div id="sumulas"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-ficha" role="tabpanel" aria-labelledby="aba-ficha" hidden>
|
||||
<h2>Ficha do time</h2>
|
||||
<p id="ficha-aviso" class="explica"></p>
|
||||
<form id="ficha" novalidate>
|
||||
<fieldset>
|
||||
<legend>Jogadores</legend>
|
||||
<div id="jogadores"></div>
|
||||
<button type="button" id="maisJogador" class="secundario">+ Jogador</button>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Comissão técnica</legend>
|
||||
<div id="comissao"></div>
|
||||
<datalist id="funcoes-comissao">
|
||||
<option value="Técnico"></option><option value="Auxiliar técnico"></option><option value="Preparador físico"></option>
|
||||
<option value="Preparador de goleiros"></option><option value="Médico"></option><option value="Fisioterapeuta"></option>
|
||||
<option value="Massagista"></option><option value="Roupeiro"></option><option value="Dirigente"></option>
|
||||
</datalist>
|
||||
<button type="button" id="maisComissao" class="secundario">+ Pessoa na comissão</button>
|
||||
</fieldset>
|
||||
<p id="ficha-erro" class="erro" role="alert" hidden></p>
|
||||
<p id="ficha-ok" class="ok" role="status" hidden></p>
|
||||
<button type="submit" id="salvar">Salvar ficha</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<script src="/publico/time.js?v=1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
time.js: |
|
||||
/* ===========================================================================
|
||||
* Link do time — a página que UMA pessoa do time abre pelo link com chave.
|
||||
*
|
||||
* Sem login: a chave no endereço é o único segredo. A página mostra a tabela,
|
||||
* os jogos e as súmulas da competição e a ficha do time; se o time for de
|
||||
* fora e ainda não houver jogo encerrado, a ficha pode ser corrigida aqui.
|
||||
*
|
||||
* Tudo o que vem do servidor entra na página com `textContent` — nunca como
|
||||
* HTML. O script é externo porque a CSP é `script-src 'self'`.
|
||||
* =========================================================================== */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var TOKEN = /^[A-Za-z0-9_-]{20,64}$/;
|
||||
var MAX_JOGADORES = 60;
|
||||
var MAX_COMISSAO = 15;
|
||||
var ABAS = ["tabela", "jogos", "sumulas", "ficha"];
|
||||
var ROTULOS_FORMA = { V: "Vitória", E: "Empate", D: "Derrota" };
|
||||
var ROTULOS_STATUS = {
|
||||
AGENDADA: "Agendado", ADIADA: "Adiado", EM_ANDAMENTO: "Em andamento", ENCERRADA: "Encerrado", CANCELADA: "Cancelado"
|
||||
};
|
||||
|
||||
var token = decodeURIComponent(location.pathname.split("/").filter(Boolean).pop() || "");
|
||||
var base = "/api/public/competicoes/times/" + encodeURIComponent(token);
|
||||
|
||||
var titulo = document.getElementById("titulo");
|
||||
var situacao = document.getElementById("situacao");
|
||||
var painel = document.getElementById("painel");
|
||||
var listaJogadores = document.getElementById("jogadores");
|
||||
var listaComissao = document.getElementById("comissao");
|
||||
var formFicha = document.getElementById("ficha");
|
||||
var fichaErro = document.getElementById("ficha-erro");
|
||||
var fichaOk = document.getElementById("ficha-ok");
|
||||
var salvar = document.getElementById("salvar");
|
||||
var visao = null;
|
||||
|
||||
function el(tag, classe, texto) {
|
||||
var e = document.createElement(tag);
|
||||
if (classe) e.className = classe;
|
||||
if (texto !== undefined && texto !== null) e.textContent = String(texto);
|
||||
return e;
|
||||
}
|
||||
|
||||
function limpar(no) {
|
||||
while (no.firstChild) no.removeChild(no.firstChild);
|
||||
}
|
||||
|
||||
function recusar(texto) {
|
||||
situacao.textContent = texto;
|
||||
situacao.className = "invalido";
|
||||
painel.hidden = true;
|
||||
}
|
||||
|
||||
function lerResposta(r) {
|
||||
return r.json().catch(function () { return {}; }).then(function (corpo) { return { status: r.status, corpo: corpo }; });
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ abas
|
||||
|
||||
function abrirAba(nome, focar) {
|
||||
for (var i = 0; i < ABAS.length; i++) {
|
||||
var ativa = ABAS[i] === nome;
|
||||
var aba = document.getElementById("aba-" + ABAS[i]);
|
||||
aba.setAttribute("aria-selected", ativa ? "true" : "false");
|
||||
aba.tabIndex = ativa ? 0 : -1;
|
||||
document.getElementById("sec-" + ABAS[i]).hidden = !ativa;
|
||||
if (ativa && focar) aba.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function aoTeclarNaAba(ev) {
|
||||
var i = ABAS.indexOf(ev.currentTarget.getAttribute("data-aba"));
|
||||
if (ev.key === "ArrowRight") { abrirAba(ABAS[(i + 1) % ABAS.length], true); ev.preventDefault(); }
|
||||
else if (ev.key === "ArrowLeft") { abrirAba(ABAS[(i + ABAS.length - 1) % ABAS.length], true); ev.preventDefault(); }
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ tabela
|
||||
|
||||
function dataBr(iso) {
|
||||
if (!iso) return "Data a definir";
|
||||
var d = new Date(iso);
|
||||
if (isNaN(d.getTime())) return "Data a definir";
|
||||
try {
|
||||
return d.toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" });
|
||||
} catch (e) {
|
||||
return d.toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
function percentual(n) {
|
||||
return (Math.round((n || 0) * 10) / 10).toString().replace(".", ",") + "%";
|
||||
}
|
||||
|
||||
function montarTabela(linhas, meuNome) {
|
||||
var alvo = document.getElementById("tabela");
|
||||
limpar(alvo);
|
||||
if (!linhas || !linhas.length) { alvo.appendChild(el("p", "explica", "A tabela aparece quando houver times e jogos.")); return; }
|
||||
var t = el("table");
|
||||
t.appendChild(el("caption", "explica", "Classificação"));
|
||||
var cab = el("tr");
|
||||
var cols = [["#", "Posição"], ["Time", "Time"], ["P", "Pontos"], ["J", "Jogos"], ["V", "Vitórias"], ["E", "Empates"],
|
||||
["D", "Derrotas"], ["GP", "Gols pró"], ["GC", "Gols contra"], ["SG", "Saldo de gols"], ["%", "Aproveitamento"],
|
||||
["Últimos 5", "Últimos 5 jogos"], ["CA", "Cartões amarelos"], ["CV", "Cartões vermelhos"]];
|
||||
cols.forEach(function (c) {
|
||||
var th = el("th", null, c[0]);
|
||||
th.scope = "col";
|
||||
th.title = c[1];
|
||||
cab.appendChild(th);
|
||||
});
|
||||
var thead = el("thead");
|
||||
thead.appendChild(cab);
|
||||
t.appendChild(thead);
|
||||
var corpo = el("tbody");
|
||||
linhas.forEach(function (l) {
|
||||
var tr = el("tr", l.time === meuNome ? "meu" : "");
|
||||
[l.posicao, l.time, l.pontos, l.jogos, l.vitorias, l.empates, l.derrotas, l.golsPro, l.golsContra, l.saldoGols,
|
||||
percentual(l.aproveitamento)].forEach(function (v) { tr.appendChild(el("td", null, v)); });
|
||||
var forma = el("td");
|
||||
var caixa = el("span", "forma");
|
||||
(l.ultimos || []).forEach(function (r) {
|
||||
var p = el("span", "ponto " + r);
|
||||
p.title = ROTULOS_FORMA[r] || r;
|
||||
p.setAttribute("role", "img");
|
||||
p.setAttribute("aria-label", ROTULOS_FORMA[r] || r);
|
||||
caixa.appendChild(p);
|
||||
});
|
||||
forma.appendChild(caixa);
|
||||
tr.appendChild(forma);
|
||||
var am = el("td");
|
||||
am.appendChild(el("span", "cartao amarelo"));
|
||||
am.appendChild(document.createTextNode(String(l.amarelos || 0)));
|
||||
tr.appendChild(am);
|
||||
var vm = el("td");
|
||||
vm.appendChild(el("span", "cartao vermelho"));
|
||||
vm.appendChild(document.createTextNode(String(l.vermelhos || 0)));
|
||||
tr.appendChild(vm);
|
||||
corpo.appendChild(tr);
|
||||
});
|
||||
t.appendChild(corpo);
|
||||
alvo.appendChild(t);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ jogos
|
||||
|
||||
function montarJogos(jogos) {
|
||||
var alvo = document.getElementById("jogos");
|
||||
limpar(alvo);
|
||||
if (!jogos || !jogos.length) { alvo.appendChild(el("p", "explica", "Ainda não há jogos marcados.")); return; }
|
||||
jogos.forEach(function (j) {
|
||||
var card = el("div", "jogo");
|
||||
var quando = el("div", "quando", (j.rodada ? j.rodada + " · " : "") + dataBr(j.data));
|
||||
var etiqueta = el("span", "etiqueta", ROTULOS_STATUS[j.status] || j.status);
|
||||
quando.appendChild(etiqueta);
|
||||
if (j.wo) quando.appendChild(el("span", "etiqueta", "W.O."));
|
||||
card.appendChild(quando);
|
||||
var linha = el("div", "confronto");
|
||||
linha.appendChild(el("span", null, j.mandante));
|
||||
var temPlacar = j.placarMandante !== undefined && j.placarMandante !== null;
|
||||
linha.appendChild(el("span", temPlacar ? "placar" : "", temPlacar ? j.placarMandante + " x " + j.placarVisitante : "x"));
|
||||
linha.appendChild(el("span", null, j.visitante));
|
||||
card.appendChild(linha);
|
||||
if (j.penaltis) card.appendChild(el("div", "quando", "Pênaltis: " + j.penaltis.mandante + " x " + j.penaltis.visitante));
|
||||
if (j.motivo) card.appendChild(el("div", "quando", "Motivo: " + j.motivo));
|
||||
alvo.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ súmulas
|
||||
|
||||
function pessoa(p) {
|
||||
if (!p) return "jogador não identificado";
|
||||
if (p.nome && p.numeroCamisa !== undefined && p.numeroCamisa !== null) return p.nome + " (" + p.numeroCamisa + ")";
|
||||
if (p.nome) return p.nome;
|
||||
if (p.numeroCamisa !== undefined && p.numeroCamisa !== null) return "camisa " + p.numeroCamisa;
|
||||
return "jogador não identificado";
|
||||
}
|
||||
|
||||
function momento(e) {
|
||||
var m = e.minuto !== undefined && e.minuto !== null ? e.minuto + "'" : "";
|
||||
var t = e.tempo ? e.tempo + "º tempo" : "";
|
||||
return [t, m].filter(Boolean).join(" ") || "—";
|
||||
}
|
||||
|
||||
function secaoDaSumula(card, nome, itens, texto) {
|
||||
if (!itens || !itens.length) return;
|
||||
card.appendChild(el("strong", null, nome));
|
||||
var ul = el("ul");
|
||||
itens.forEach(function (i) { ul.appendChild(el("li", null, texto(i))); });
|
||||
card.appendChild(ul);
|
||||
}
|
||||
|
||||
function montarSumulas(sumulas, jogos) {
|
||||
var alvo = document.getElementById("sumulas");
|
||||
limpar(alvo);
|
||||
if (!sumulas || !sumulas.length) { alvo.appendChild(el("p", "explica", "As súmulas dos jogos do seu time aparecem aqui.")); return; }
|
||||
sumulas.forEach(function (s) {
|
||||
var j = (jogos || []).filter(function (x) { return x.ref === s.jogoRef; })[0];
|
||||
var card = el("div", "sumula");
|
||||
var cab = j ? j.mandante + " x " + j.visitante : "Jogo " + s.jogoRef;
|
||||
if (j && j.placarMandante !== undefined && j.placarMandante !== null) {
|
||||
cab = j.mandante + " " + j.placarMandante + " x " + j.placarVisitante + " " + j.visitante;
|
||||
}
|
||||
card.appendChild(el("h3", null, cab));
|
||||
if (j) card.appendChild(el("div", "quando", (j.rodada ? j.rodada + " · " : "") + dataBr(j.data)));
|
||||
secaoDaSumula(card, "Gols", s.gols, function (g) { return momento(g) + " · " + g.time + " · " + pessoa(g.jogador); });
|
||||
secaoDaSumula(card, "Cartões", s.cartoes, function (c) {
|
||||
return momento(c) + " · " + (c.tipo === "VERMELHO" ? "vermelho" : "amarelo") + " · " + c.time + " · " + pessoa(c.jogador);
|
||||
});
|
||||
secaoDaSumula(card, "Substituições", s.substituicoes, function (t) {
|
||||
return momento(t) + " · " + t.time + " · sai " + pessoa(t.sai) + ", entra " + pessoa(t.entra);
|
||||
});
|
||||
alvo.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ ficha
|
||||
|
||||
function linhaDeJogador(j) {
|
||||
var linha = el("div", "linha-ficha linha-jogador");
|
||||
if (j && j.id) linha.setAttribute("data-id", j.id);
|
||||
var nome = el("input");
|
||||
nome.placeholder = "Nome do jogador";
|
||||
nome.maxLength = 120;
|
||||
nome.value = (j && j.nome) || "";
|
||||
nome.setAttribute("aria-label", "Nome do jogador");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
var camisa = el("input");
|
||||
camisa.type = "number";
|
||||
camisa.min = "0";
|
||||
camisa.max = "999";
|
||||
camisa.placeholder = "nº";
|
||||
camisa.value = j && j.numeroCamisa !== undefined && j.numeroCamisa !== null ? j.numeroCamisa : "";
|
||||
camisa.setAttribute("aria-label", "Número da camisa");
|
||||
camisa.setAttribute("data-campo", "camisa");
|
||||
var tirar = el("button", "secundario", "×");
|
||||
tirar.type = "button";
|
||||
tirar.setAttribute("aria-label", "Remover jogador");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(camisa);
|
||||
linha.appendChild(tirar);
|
||||
listaJogadores.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
function linhaDeComissao(m) {
|
||||
var linha = el("div", "linha-ficha linha-comissao");
|
||||
if (m && m.id) linha.setAttribute("data-id", m.id);
|
||||
var nome = el("input");
|
||||
nome.placeholder = "Nome";
|
||||
nome.maxLength = 120;
|
||||
nome.value = (m && m.nome) || "";
|
||||
nome.setAttribute("aria-label", "Nome na comissão técnica");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
var papel = el("input");
|
||||
papel.placeholder = "Função";
|
||||
papel.maxLength = 40;
|
||||
papel.value = (m && m.funcao) || "";
|
||||
papel.setAttribute("list", "funcoes-comissao");
|
||||
papel.setAttribute("aria-label", "Função na comissão técnica");
|
||||
papel.setAttribute("data-campo", "funcao");
|
||||
var tirar = el("button", "secundario", "×");
|
||||
tirar.type = "button";
|
||||
tirar.setAttribute("aria-label", "Remover da comissão");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(papel);
|
||||
linha.appendChild(tirar);
|
||||
listaComissao.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
/** Time da escola ou ficha fechada: só leitura (listas simples). */
|
||||
function fichaSomenteLeitura(ficha) {
|
||||
formFicha.hidden = true;
|
||||
var alvo = document.getElementById("ficha-leitura");
|
||||
if (!alvo) {
|
||||
alvo = el("div", "somente-leitura");
|
||||
alvo.id = "ficha-leitura";
|
||||
formFicha.parentNode.appendChild(alvo);
|
||||
}
|
||||
limpar(alvo);
|
||||
alvo.appendChild(el("h3", null, "Jogadores"));
|
||||
var ul = el("ul");
|
||||
(ficha.jogadores || []).forEach(function (j) {
|
||||
ul.appendChild(el("li", null, pessoa({ nome: j.nome, numeroCamisa: j.numeroCamisa })));
|
||||
});
|
||||
if (!ul.childNodes.length) ul.appendChild(el("li", null, "Nenhum jogador na ficha."));
|
||||
alvo.appendChild(ul);
|
||||
if ((ficha.comissao || []).length) {
|
||||
alvo.appendChild(el("h3", null, "Comissão técnica"));
|
||||
var uc = el("ul");
|
||||
ficha.comissao.forEach(function (m) { uc.appendChild(el("li", null, m.nome + " — " + m.funcao)); });
|
||||
alvo.appendChild(uc);
|
||||
}
|
||||
}
|
||||
|
||||
function montarFicha(ficha) {
|
||||
var aviso = document.getElementById("ficha-aviso");
|
||||
limpar(listaJogadores);
|
||||
limpar(listaComissao);
|
||||
if (!ficha.editavel) {
|
||||
aviso.textContent = "Esta ficha está fechada para alterações aqui. Se algo estiver errado, fale com a organização.";
|
||||
fichaSomenteLeitura(ficha);
|
||||
return;
|
||||
}
|
||||
aviso.textContent = "Confira os jogadores (nome e número da camisa) e a comissão técnica. Dá para corrigir até o primeiro jogo ser encerrado.";
|
||||
formFicha.hidden = false;
|
||||
(ficha.jogadores || []).forEach(linhaDeJogador);
|
||||
if (!(ficha.jogadores || []).length) for (var i = 0; i < 3; i++) linhaDeJogador();
|
||||
(ficha.comissao || []).forEach(linhaDeComissao);
|
||||
}
|
||||
|
||||
function mostrarErroFicha(texto) {
|
||||
fichaErro.textContent = texto || "";
|
||||
fichaErro.hidden = !texto;
|
||||
}
|
||||
|
||||
function mostrarOkFicha(texto) {
|
||||
fichaOk.textContent = texto || "";
|
||||
fichaOk.hidden = !texto;
|
||||
}
|
||||
|
||||
function corpoDaFicha() {
|
||||
var jogadores = [];
|
||||
var linhas = listaJogadores.querySelectorAll(".linha-jogador");
|
||||
for (var i = 0; i < linhas.length; i++) {
|
||||
var nome = linhas[i].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!nome) continue;
|
||||
var c = linhas[i].querySelector('[data-campo="camisa"]').value;
|
||||
var j = { nome: nome, numeroCamisa: c === "" ? null : parseInt(c, 10) };
|
||||
if (linhas[i].getAttribute("data-id")) j.id = linhas[i].getAttribute("data-id");
|
||||
jogadores.push(j);
|
||||
}
|
||||
var comissao = [];
|
||||
var faltaFuncao = false;
|
||||
var pessoas = listaComissao.querySelectorAll(".linha-comissao");
|
||||
for (var k = 0; k < pessoas.length; k++) {
|
||||
var n = pessoas[k].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!n) continue;
|
||||
var f = pessoas[k].querySelector('[data-campo="funcao"]').value.trim();
|
||||
if (!f) faltaFuncao = true;
|
||||
var m = { nome: n, funcao: f };
|
||||
if (pessoas[k].getAttribute("data-id")) m.id = pessoas[k].getAttribute("data-id");
|
||||
comissao.push(m);
|
||||
}
|
||||
return { jogadores: jogadores, comissao: comissao, faltaFuncao: faltaFuncao };
|
||||
}
|
||||
|
||||
function aoSalvar(ev) {
|
||||
ev.preventDefault();
|
||||
mostrarErroFicha("");
|
||||
mostrarOkFicha("");
|
||||
var c = corpoDaFicha();
|
||||
if (c.jogadores.length > MAX_JOGADORES) { mostrarErroFicha("No máximo " + MAX_JOGADORES + " jogadores."); return; }
|
||||
if (c.faltaFuncao) { mostrarErroFicha("Informe a função de cada pessoa da comissão técnica."); return; }
|
||||
if (c.comissao.length > MAX_COMISSAO) { mostrarErroFicha("No máximo " + MAX_COMISSAO + " pessoas na comissão técnica."); return; }
|
||||
salvar.disabled = true;
|
||||
salvar.setAttribute("aria-busy", "true");
|
||||
fetch(base + "/ficha", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ jogadores: c.jogadores, comissao: c.comissao })
|
||||
}).then(lerResposta).then(function (r) {
|
||||
salvar.disabled = false;
|
||||
salvar.removeAttribute("aria-busy");
|
||||
if (r.status >= 200 && r.status < 300) {
|
||||
mostrarOkFicha("Ficha salva.");
|
||||
return carregar(true);
|
||||
}
|
||||
if (r.status === 404) { recusar("Este link não está mais disponível."); return null; }
|
||||
mostrarErroFicha((r.corpo && r.corpo.detail) || "Não foi possível salvar agora. Tente de novo em instantes.");
|
||||
return null;
|
||||
}).catch(function () {
|
||||
salvar.disabled = false;
|
||||
salvar.removeAttribute("aria-busy");
|
||||
mostrarErroFicha("Sem conexão. Confira a internet e tente de novo.");
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ carga
|
||||
|
||||
function mostrar(v, manterAba) {
|
||||
visao = v;
|
||||
var nomeTime = (v.time && v.time.nome) || "Seu time";
|
||||
titulo.textContent = (v.competicao || "Competição") + " — " + nomeTime;
|
||||
document.title = titulo.textContent + " — Athletic Map";
|
||||
var partes = [];
|
||||
if (v.time && v.time.escola) partes.push(v.time.escola);
|
||||
if (v.time && v.time.cidade) partes.push(v.time.cidade);
|
||||
if (v.temporada && v.temporada.nome) partes.push("Temporada " + v.temporada.nome);
|
||||
if (v.time && v.time.status === "DESQUALIFICADO") partes.push("Time desqualificado");
|
||||
situacao.textContent = partes.join(" · ");
|
||||
situacao.className = "";
|
||||
painel.hidden = false;
|
||||
montarTabela(v.classificacao, nomeTime);
|
||||
montarJogos(v.jogos);
|
||||
montarSumulas(v.sumulas, v.jogos);
|
||||
montarFicha(v.ficha || { jogadores: [], comissao: [], editavel: false });
|
||||
if (!manterAba) abrirAba("tabela", false);
|
||||
}
|
||||
|
||||
function carregar(manterAba) {
|
||||
return fetch(base).then(lerResposta).then(function (r) {
|
||||
if (r.status === 404) { recusar("Este link não está disponível ou foi desativado. Peça um novo à organização."); return; }
|
||||
if (r.status === 429) { recusar("Muitas consultas seguidas. Aguarde alguns minutos e abra o link de novo."); return; }
|
||||
if (r.status !== 200) { recusar("Não foi possível abrir agora. Tente de novo em instantes."); return; }
|
||||
mostrar(r.corpo || {}, manterAba);
|
||||
}).catch(function () {
|
||||
recusar("Não foi possível abrir agora. Tente de novo em instantes.");
|
||||
});
|
||||
}
|
||||
|
||||
function iniciar() {
|
||||
if (!TOKEN.test(token)) { recusar("Este endereço não é válido."); return; }
|
||||
ABAS.forEach(function (nome) {
|
||||
var aba = document.getElementById("aba-" + nome);
|
||||
aba.addEventListener("click", function () { abrirAba(nome, false); });
|
||||
aba.addEventListener("keydown", aoTeclarNaAba);
|
||||
});
|
||||
document.getElementById("maisJogador").addEventListener("click", function () {
|
||||
if (listaJogadores.querySelectorAll(".linha-jogador").length < MAX_JOGADORES) linhaDeJogador().querySelector("input").focus();
|
||||
});
|
||||
document.getElementById("maisComissao").addEventListener("click", function () {
|
||||
if (listaComissao.querySelectorAll(".linha-comissao").length < MAX_COMISSAO) linhaDeComissao(null).querySelector("input").focus();
|
||||
});
|
||||
formFicha.addEventListener("submit", aoSalvar);
|
||||
carregar(false);
|
||||
}
|
||||
|
||||
iniciar();
|
||||
})();
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
|
||||
@@ -150,6 +150,9 @@ spec:
|
||||
- { key: "preparando.js", path: "preparando.js" }
|
||||
- { key: "privacidade.html", path: "privacidade.html" }
|
||||
- { key: "termos.html", path: "termos.html" }
|
||||
- { key: "time.css", path: "time.css" }
|
||||
- { key: "time.html", path: "time.html" }
|
||||
- { key: "time.js", path: "time.js" }
|
||||
- configMap:
|
||||
name: frontend-publico-binario
|
||||
items:
|
||||
|
||||
@@ -409,7 +409,7 @@ data:
|
||||
|
||||
<form id="formulario" hidden novalidate>
|
||||
<p class="explica">
|
||||
Preencha os dados do seu time e dos jogadores (só o nome e o número da camisa).
|
||||
Preencha os dados do seu time, dos jogadores (só o nome e o número da camisa) e da comissão técnica (nome e função).
|
||||
A organização confere e aprova antes do time entrar na competição.
|
||||
</p>
|
||||
|
||||
@@ -433,12 +433,23 @@ data:
|
||||
<button type="button" id="maisJogador" class="secundario">+ Jogador</button>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Comissão técnica</legend>
|
||||
<div id="comissao"></div>
|
||||
<datalist id="funcoes-comissao">
|
||||
<option value="Técnico"></option><option value="Auxiliar técnico"></option><option value="Preparador físico"></option>
|
||||
<option value="Preparador de goleiros"></option><option value="Médico"></option><option value="Fisioterapeuta"></option>
|
||||
<option value="Massagista"></option><option value="Roupeiro"></option><option value="Dirigente"></option>
|
||||
</datalist>
|
||||
<button type="button" id="maisComissao" class="secundario">+ Pessoa na comissão</button>
|
||||
</fieldset>
|
||||
|
||||
<p id="erro" class="erro" role="alert" hidden></p>
|
||||
<button type="submit" id="enviar">Enviar time</button>
|
||||
</form>
|
||||
</main>
|
||||
<script src="/publico/desafio.js?v=1"></script>
|
||||
<script src="/publico/convite.js?v=2"></script>
|
||||
<script src="/publico/convite.js?v=3"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -465,6 +476,7 @@ data:
|
||||
|
||||
var TOKEN = /^[A-Za-z0-9_-]{20,64}$/;
|
||||
var MAX_JOGADORES = 60;
|
||||
var MAX_COMISSAO = 15;
|
||||
|
||||
var token = decodeURIComponent((location.pathname.split("/").filter(Boolean).pop() || ""));
|
||||
var base = "/api/public/competicoes/convites/" + encodeURIComponent(token);
|
||||
@@ -473,6 +485,7 @@ data:
|
||||
var situacao = document.getElementById("situacao");
|
||||
var formulario = document.getElementById("formulario");
|
||||
var listaJogadores = document.getElementById("jogadores");
|
||||
var listaComissao = document.getElementById("comissao");
|
||||
var erro = document.getElementById("erro");
|
||||
var enviar = document.getElementById("enviar");
|
||||
var ROTULO_ENVIAR = enviar.textContent;
|
||||
@@ -520,6 +533,39 @@ data:
|
||||
return linha;
|
||||
}
|
||||
|
||||
/** Comissao tecnica (21/10/2026): nome e funcao; a funcao tem sugestoes, mas aceita texto livre. */
|
||||
function linhaDeComissao(funcao) {
|
||||
var linha = document.createElement("div");
|
||||
linha.className = "linha-jogador linha-comissao";
|
||||
|
||||
var nome = document.createElement("input");
|
||||
nome.placeholder = "Nome";
|
||||
nome.maxLength = 120;
|
||||
nome.setAttribute("aria-label", "Nome na comissão técnica");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
|
||||
var papel = document.createElement("input");
|
||||
papel.placeholder = "Função";
|
||||
papel.maxLength = 40;
|
||||
papel.value = funcao || "";
|
||||
papel.setAttribute("list", "funcoes-comissao");
|
||||
papel.setAttribute("aria-label", "Função na comissão técnica");
|
||||
papel.setAttribute("data-campo", "funcao");
|
||||
|
||||
var tirar = document.createElement("button");
|
||||
tirar.type = "button";
|
||||
tirar.className = "secundario";
|
||||
tirar.textContent = "×";
|
||||
tirar.setAttribute("aria-label", "Remover da comissão");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(papel);
|
||||
linha.appendChild(tirar);
|
||||
listaComissao.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
function valor(id) {
|
||||
var v = document.getElementById(id).value.trim();
|
||||
return v === "" ? null : v;
|
||||
@@ -534,14 +580,26 @@ data:
|
||||
var c = linhas[i].querySelector('[data-campo="camisa"]').value;
|
||||
jogadores.push({ nome: nome, numeroCamisa: c === "" ? null : parseInt(c, 10) });
|
||||
}
|
||||
var comissao = [];
|
||||
var faltaFuncao = false;
|
||||
var pessoas = listaComissao.querySelectorAll(".linha-comissao");
|
||||
for (var k = 0; k < pessoas.length; k++) {
|
||||
var n = pessoas[k].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!n) continue;
|
||||
var f = pessoas[k].querySelector('[data-campo="funcao"]').value.trim();
|
||||
if (!f) faltaFuncao = true;
|
||||
comissao.push({ nome: n, funcao: f });
|
||||
}
|
||||
return {
|
||||
faltaFuncao: faltaFuncao,
|
||||
nome: valor("nome"),
|
||||
escola: valor("escola"),
|
||||
cidade: valor("cidade"),
|
||||
contatoNome: valor("contatoNome"),
|
||||
contatoTelefone: valor("contatoTelefone"),
|
||||
contatoEmail: valor("contatoEmail"),
|
||||
jogadores: jogadores
|
||||
jogadores: jogadores,
|
||||
comissao: comissao
|
||||
};
|
||||
}
|
||||
|
||||
@@ -555,6 +613,9 @@ data:
|
||||
var corpo = montarCorpo();
|
||||
if (!corpo.nome) { mostrarErro("Informe o nome do time."); return; }
|
||||
if (corpo.jogadores.length > MAX_JOGADORES) { mostrarErro("No máximo " + MAX_JOGADORES + " jogadores."); return; }
|
||||
if (corpo.faltaFuncao) { mostrarErro("Informe a função de cada pessoa da comissão técnica."); return; }
|
||||
if (corpo.comissao.length > MAX_COMISSAO) { mostrarErro("No máximo " + MAX_COMISSAO + " pessoas na comissão técnica."); return; }
|
||||
delete corpo.faltaFuncao;
|
||||
enviar.disabled = true;
|
||||
enviar.textContent = "Verificando…";
|
||||
enviar.setAttribute("aria-busy", "true");
|
||||
@@ -615,6 +676,7 @@ data:
|
||||
situacao.className = "";
|
||||
formulario.hidden = false;
|
||||
for (var i = 0; i < 5; i++) linhaDeJogador();
|
||||
linhaDeComissao("Técnico");
|
||||
}).catch(function () {
|
||||
recusar("Não foi possível abrir o convite agora. Tente de novo em instantes.");
|
||||
});
|
||||
@@ -623,6 +685,9 @@ data:
|
||||
document.getElementById("maisJogador").addEventListener("click", function () {
|
||||
if (listaJogadores.querySelectorAll(".linha-jogador").length < MAX_JOGADORES) linhaDeJogador().querySelector("input").focus();
|
||||
});
|
||||
document.getElementById("maisComissao").addEventListener("click", function () {
|
||||
if (listaComissao.querySelectorAll(".linha-comissao").length < MAX_COMISSAO) linhaDeComissao("").querySelector("input").focus();
|
||||
});
|
||||
formulario.addEventListener("submit", aoEnviar);
|
||||
iniciar();
|
||||
})();
|
||||
@@ -8242,6 +8307,557 @@ data:
|
||||
</body>
|
||||
</html>
|
||||
|
||||
time.css: |
|
||||
/* Link do time: página pública, sem fonte nem script de fora (a CSP só deixa 'self'). Mobile primeiro. */
|
||||
main { max-width: 56rem; }
|
||||
.explica, .legenda { color: var(--muted); font-size: .9rem; }
|
||||
.abas { display: flex; gap: .25rem; overflow-x: auto; border-bottom: 2px solid var(--line); margin: 0 0 1rem; }
|
||||
.abas button { background: transparent; color: var(--navy); border: 0; border-bottom: 3px solid transparent; border-radius: 0; padding: .6rem .9rem; white-space: nowrap; font: inherit; font-weight: 700; cursor: pointer; }
|
||||
.abas button[aria-selected="true"] { border-bottom-color: var(--orange); }
|
||||
.abas button:focus-visible, button:focus-visible, input:focus-visible { outline: 3px solid var(--orange); outline-offset: 1px; }
|
||||
h2 { font-size: 1.15rem; margin: .5rem 0; }
|
||||
.rolagem { overflow-x: auto; }
|
||||
table { border-collapse: collapse; width: 100%; font-size: .9rem; background: #fff; }
|
||||
th, td { padding: .4rem .45rem; text-align: center; border-bottom: 1px solid var(--line); white-space: nowrap; }
|
||||
th:nth-child(2), td:nth-child(2) { text-align: left; white-space: normal; min-width: 8rem; }
|
||||
tr.meu { background: #fff4e5; font-weight: 700; }
|
||||
.forma { display: inline-flex; gap: 3px; vertical-align: middle; }
|
||||
.ponto { width: .8rem; height: .8rem; border-radius: 50%; display: inline-block; }
|
||||
.ponto.V { background: #067647; }
|
||||
.ponto.E { background: #98a2b3; }
|
||||
.ponto.D { background: #b42318; }
|
||||
.cartao { display: inline-block; width: .7rem; height: .95rem; border-radius: 2px; vertical-align: middle; margin-right: 3px; }
|
||||
.cartao.amarelo { background: #f5c518; }
|
||||
.cartao.vermelho { background: #d92d20; }
|
||||
.jogo, .sumula { background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: .6rem .8rem; margin: 0 0 .6rem; }
|
||||
.jogo .quando { color: var(--muted); font-size: .85rem; }
|
||||
.jogo .confronto { display: grid; grid-template-columns: 1fr auto 1fr; gap: .5rem; align-items: center; font-weight: 700; }
|
||||
.jogo .confronto span:first-child { text-align: right; }
|
||||
.jogo .placar { background: var(--navy); color: #fff; border-radius: 6px; padding: .1rem .6rem; }
|
||||
.etiqueta { display: inline-block; font-size: .75rem; font-weight: 700; padding: .05rem .5rem; border-radius: 99px; background: #fff1e3; border: 1px solid var(--orange); margin-left: .4rem; }
|
||||
.sumula h3 { margin: 0 0 .3rem; font-size: 1rem; }
|
||||
.sumula ul { margin: .2rem 0 .5rem; padding-left: 1.2rem; }
|
||||
form { display: grid; gap: 1rem; }
|
||||
fieldset { border: 1px solid var(--line); border-radius: 8px; background: #fff; padding: .75rem 1rem 1rem; display: grid; gap: .6rem; }
|
||||
legend { font-weight: 700; padding: 0 .4rem; }
|
||||
input { font: inherit; padding: .5rem .6rem; border: 1px solid var(--line); border-radius: 6px; width: 100%; }
|
||||
.linha-ficha { display: grid; grid-template-columns: 1fr 5.5rem auto; gap: .4rem; align-items: center; }
|
||||
.linha-ficha button { padding: .4rem .6rem; }
|
||||
button { font: inherit; font-weight: 700; padding: .65rem 1.1rem; border: 0; border-radius: 8px; background: var(--orange); color: #fff; cursor: pointer; }
|
||||
button.secundario { background: #fff; color: var(--navy); border: 1px solid var(--line); justify-self: start; }
|
||||
button:disabled { opacity: .55; cursor: not-allowed; }
|
||||
.erro { color: #b42318; margin: 0; font-weight: 600; }
|
||||
.ok { background: #e7f6ec; border-left: 4px solid #067647; padding: .8rem 1rem; margin: 0; }
|
||||
#situacao.invalido { background: #fff1e3; border-left: 4px solid var(--orange); padding: 1rem 1.2rem; }
|
||||
.somente-leitura li { margin: .15rem 0; }
|
||||
@media (max-width: 30rem) { .linha-ficha { grid-template-columns: 1fr 4.5rem auto; } }
|
||||
|
||||
time.html: |
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<title>Seu time na competição — Athletic Map</title>
|
||||
<link rel="stylesheet" href="/publico/_estilo.css">
|
||||
<link rel="stylesheet" href="/publico/time.css?v=1">
|
||||
</head>
|
||||
<body>
|
||||
<header><div class="marca">Athletic <span>Map</span></div></header>
|
||||
<main>
|
||||
<h1 id="titulo">Seu time na competição</h1>
|
||||
<p id="situacao" role="status" aria-live="polite">Abrindo o link…</p>
|
||||
|
||||
<div id="painel" hidden>
|
||||
<div class="abas" role="tablist" aria-label="Seções">
|
||||
<button type="button" role="tab" id="aba-tabela" aria-controls="sec-tabela" aria-selected="true" data-aba="tabela">Tabela</button>
|
||||
<button type="button" role="tab" id="aba-jogos" aria-controls="sec-jogos" aria-selected="false" tabindex="-1" data-aba="jogos">Jogos</button>
|
||||
<button type="button" role="tab" id="aba-sumulas" aria-controls="sec-sumulas" aria-selected="false" tabindex="-1" data-aba="sumulas">Súmulas</button>
|
||||
<button type="button" role="tab" id="aba-ficha" aria-controls="sec-ficha" aria-selected="false" tabindex="-1" data-aba="ficha">Ficha do time</button>
|
||||
</div>
|
||||
|
||||
<section id="sec-tabela" role="tabpanel" aria-labelledby="aba-tabela">
|
||||
<h2>Tabela</h2>
|
||||
<div class="rolagem" id="tabela"></div>
|
||||
<p class="legenda">P pontos · J jogos · V vitórias · E empates · D derrotas · GP gols pró · GC gols contra · SG saldo de gols · % aproveitamento · CA cartões amarelos · CV cartões vermelhos</p>
|
||||
</section>
|
||||
|
||||
<section id="sec-jogos" role="tabpanel" aria-labelledby="aba-jogos" hidden>
|
||||
<h2>Jogos</h2>
|
||||
<div id="jogos"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-sumulas" role="tabpanel" aria-labelledby="aba-sumulas" hidden>
|
||||
<h2>Súmulas</h2>
|
||||
<div id="sumulas"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-ficha" role="tabpanel" aria-labelledby="aba-ficha" hidden>
|
||||
<h2>Ficha do time</h2>
|
||||
<p id="ficha-aviso" class="explica"></p>
|
||||
<form id="ficha" novalidate>
|
||||
<fieldset>
|
||||
<legend>Jogadores</legend>
|
||||
<div id="jogadores"></div>
|
||||
<button type="button" id="maisJogador" class="secundario">+ Jogador</button>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Comissão técnica</legend>
|
||||
<div id="comissao"></div>
|
||||
<datalist id="funcoes-comissao">
|
||||
<option value="Técnico"></option><option value="Auxiliar técnico"></option><option value="Preparador físico"></option>
|
||||
<option value="Preparador de goleiros"></option><option value="Médico"></option><option value="Fisioterapeuta"></option>
|
||||
<option value="Massagista"></option><option value="Roupeiro"></option><option value="Dirigente"></option>
|
||||
</datalist>
|
||||
<button type="button" id="maisComissao" class="secundario">+ Pessoa na comissão</button>
|
||||
</fieldset>
|
||||
<p id="ficha-erro" class="erro" role="alert" hidden></p>
|
||||
<p id="ficha-ok" class="ok" role="status" hidden></p>
|
||||
<button type="submit" id="salvar">Salvar ficha</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<script src="/publico/time.js?v=1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
time.js: |
|
||||
/* ===========================================================================
|
||||
* Link do time — a página que UMA pessoa do time abre pelo link com chave.
|
||||
*
|
||||
* Sem login: a chave no endereço é o único segredo. A página mostra a tabela,
|
||||
* os jogos e as súmulas da competição e a ficha do time; se o time for de
|
||||
* fora e ainda não houver jogo encerrado, a ficha pode ser corrigida aqui.
|
||||
*
|
||||
* Tudo o que vem do servidor entra na página com `textContent` — nunca como
|
||||
* HTML. O script é externo porque a CSP é `script-src 'self'`.
|
||||
* =========================================================================== */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var TOKEN = /^[A-Za-z0-9_-]{20,64}$/;
|
||||
var MAX_JOGADORES = 60;
|
||||
var MAX_COMISSAO = 15;
|
||||
var ABAS = ["tabela", "jogos", "sumulas", "ficha"];
|
||||
var ROTULOS_FORMA = { V: "Vitória", E: "Empate", D: "Derrota" };
|
||||
var ROTULOS_STATUS = {
|
||||
AGENDADA: "Agendado", ADIADA: "Adiado", EM_ANDAMENTO: "Em andamento", ENCERRADA: "Encerrado", CANCELADA: "Cancelado"
|
||||
};
|
||||
|
||||
var token = decodeURIComponent(location.pathname.split("/").filter(Boolean).pop() || "");
|
||||
var base = "/api/public/competicoes/times/" + encodeURIComponent(token);
|
||||
|
||||
var titulo = document.getElementById("titulo");
|
||||
var situacao = document.getElementById("situacao");
|
||||
var painel = document.getElementById("painel");
|
||||
var listaJogadores = document.getElementById("jogadores");
|
||||
var listaComissao = document.getElementById("comissao");
|
||||
var formFicha = document.getElementById("ficha");
|
||||
var fichaErro = document.getElementById("ficha-erro");
|
||||
var fichaOk = document.getElementById("ficha-ok");
|
||||
var salvar = document.getElementById("salvar");
|
||||
var visao = null;
|
||||
|
||||
function el(tag, classe, texto) {
|
||||
var e = document.createElement(tag);
|
||||
if (classe) e.className = classe;
|
||||
if (texto !== undefined && texto !== null) e.textContent = String(texto);
|
||||
return e;
|
||||
}
|
||||
|
||||
function limpar(no) {
|
||||
while (no.firstChild) no.removeChild(no.firstChild);
|
||||
}
|
||||
|
||||
function recusar(texto) {
|
||||
situacao.textContent = texto;
|
||||
situacao.className = "invalido";
|
||||
painel.hidden = true;
|
||||
}
|
||||
|
||||
function lerResposta(r) {
|
||||
return r.json().catch(function () { return {}; }).then(function (corpo) { return { status: r.status, corpo: corpo }; });
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ abas
|
||||
|
||||
function abrirAba(nome, focar) {
|
||||
for (var i = 0; i < ABAS.length; i++) {
|
||||
var ativa = ABAS[i] === nome;
|
||||
var aba = document.getElementById("aba-" + ABAS[i]);
|
||||
aba.setAttribute("aria-selected", ativa ? "true" : "false");
|
||||
aba.tabIndex = ativa ? 0 : -1;
|
||||
document.getElementById("sec-" + ABAS[i]).hidden = !ativa;
|
||||
if (ativa && focar) aba.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function aoTeclarNaAba(ev) {
|
||||
var i = ABAS.indexOf(ev.currentTarget.getAttribute("data-aba"));
|
||||
if (ev.key === "ArrowRight") { abrirAba(ABAS[(i + 1) % ABAS.length], true); ev.preventDefault(); }
|
||||
else if (ev.key === "ArrowLeft") { abrirAba(ABAS[(i + ABAS.length - 1) % ABAS.length], true); ev.preventDefault(); }
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ tabela
|
||||
|
||||
function dataBr(iso) {
|
||||
if (!iso) return "Data a definir";
|
||||
var d = new Date(iso);
|
||||
if (isNaN(d.getTime())) return "Data a definir";
|
||||
try {
|
||||
return d.toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" });
|
||||
} catch (e) {
|
||||
return d.toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
function percentual(n) {
|
||||
return (Math.round((n || 0) * 10) / 10).toString().replace(".", ",") + "%";
|
||||
}
|
||||
|
||||
function montarTabela(linhas, meuNome) {
|
||||
var alvo = document.getElementById("tabela");
|
||||
limpar(alvo);
|
||||
if (!linhas || !linhas.length) { alvo.appendChild(el("p", "explica", "A tabela aparece quando houver times e jogos.")); return; }
|
||||
var t = el("table");
|
||||
t.appendChild(el("caption", "explica", "Classificação"));
|
||||
var cab = el("tr");
|
||||
var cols = [["#", "Posição"], ["Time", "Time"], ["P", "Pontos"], ["J", "Jogos"], ["V", "Vitórias"], ["E", "Empates"],
|
||||
["D", "Derrotas"], ["GP", "Gols pró"], ["GC", "Gols contra"], ["SG", "Saldo de gols"], ["%", "Aproveitamento"],
|
||||
["Últimos 5", "Últimos 5 jogos"], ["CA", "Cartões amarelos"], ["CV", "Cartões vermelhos"]];
|
||||
cols.forEach(function (c) {
|
||||
var th = el("th", null, c[0]);
|
||||
th.scope = "col";
|
||||
th.title = c[1];
|
||||
cab.appendChild(th);
|
||||
});
|
||||
var thead = el("thead");
|
||||
thead.appendChild(cab);
|
||||
t.appendChild(thead);
|
||||
var corpo = el("tbody");
|
||||
linhas.forEach(function (l) {
|
||||
var tr = el("tr", l.time === meuNome ? "meu" : "");
|
||||
[l.posicao, l.time, l.pontos, l.jogos, l.vitorias, l.empates, l.derrotas, l.golsPro, l.golsContra, l.saldoGols,
|
||||
percentual(l.aproveitamento)].forEach(function (v) { tr.appendChild(el("td", null, v)); });
|
||||
var forma = el("td");
|
||||
var caixa = el("span", "forma");
|
||||
(l.ultimos || []).forEach(function (r) {
|
||||
var p = el("span", "ponto " + r);
|
||||
p.title = ROTULOS_FORMA[r] || r;
|
||||
p.setAttribute("role", "img");
|
||||
p.setAttribute("aria-label", ROTULOS_FORMA[r] || r);
|
||||
caixa.appendChild(p);
|
||||
});
|
||||
forma.appendChild(caixa);
|
||||
tr.appendChild(forma);
|
||||
var am = el("td");
|
||||
am.appendChild(el("span", "cartao amarelo"));
|
||||
am.appendChild(document.createTextNode(String(l.amarelos || 0)));
|
||||
tr.appendChild(am);
|
||||
var vm = el("td");
|
||||
vm.appendChild(el("span", "cartao vermelho"));
|
||||
vm.appendChild(document.createTextNode(String(l.vermelhos || 0)));
|
||||
tr.appendChild(vm);
|
||||
corpo.appendChild(tr);
|
||||
});
|
||||
t.appendChild(corpo);
|
||||
alvo.appendChild(t);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ jogos
|
||||
|
||||
function montarJogos(jogos) {
|
||||
var alvo = document.getElementById("jogos");
|
||||
limpar(alvo);
|
||||
if (!jogos || !jogos.length) { alvo.appendChild(el("p", "explica", "Ainda não há jogos marcados.")); return; }
|
||||
jogos.forEach(function (j) {
|
||||
var card = el("div", "jogo");
|
||||
var quando = el("div", "quando", (j.rodada ? j.rodada + " · " : "") + dataBr(j.data));
|
||||
var etiqueta = el("span", "etiqueta", ROTULOS_STATUS[j.status] || j.status);
|
||||
quando.appendChild(etiqueta);
|
||||
if (j.wo) quando.appendChild(el("span", "etiqueta", "W.O."));
|
||||
card.appendChild(quando);
|
||||
var linha = el("div", "confronto");
|
||||
linha.appendChild(el("span", null, j.mandante));
|
||||
var temPlacar = j.placarMandante !== undefined && j.placarMandante !== null;
|
||||
linha.appendChild(el("span", temPlacar ? "placar" : "", temPlacar ? j.placarMandante + " x " + j.placarVisitante : "x"));
|
||||
linha.appendChild(el("span", null, j.visitante));
|
||||
card.appendChild(linha);
|
||||
if (j.penaltis) card.appendChild(el("div", "quando", "Pênaltis: " + j.penaltis.mandante + " x " + j.penaltis.visitante));
|
||||
if (j.motivo) card.appendChild(el("div", "quando", "Motivo: " + j.motivo));
|
||||
alvo.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ súmulas
|
||||
|
||||
function pessoa(p) {
|
||||
if (!p) return "jogador não identificado";
|
||||
if (p.nome && p.numeroCamisa !== undefined && p.numeroCamisa !== null) return p.nome + " (" + p.numeroCamisa + ")";
|
||||
if (p.nome) return p.nome;
|
||||
if (p.numeroCamisa !== undefined && p.numeroCamisa !== null) return "camisa " + p.numeroCamisa;
|
||||
return "jogador não identificado";
|
||||
}
|
||||
|
||||
function momento(e) {
|
||||
var m = e.minuto !== undefined && e.minuto !== null ? e.minuto + "'" : "";
|
||||
var t = e.tempo ? e.tempo + "º tempo" : "";
|
||||
return [t, m].filter(Boolean).join(" ") || "—";
|
||||
}
|
||||
|
||||
function secaoDaSumula(card, nome, itens, texto) {
|
||||
if (!itens || !itens.length) return;
|
||||
card.appendChild(el("strong", null, nome));
|
||||
var ul = el("ul");
|
||||
itens.forEach(function (i) { ul.appendChild(el("li", null, texto(i))); });
|
||||
card.appendChild(ul);
|
||||
}
|
||||
|
||||
function montarSumulas(sumulas, jogos) {
|
||||
var alvo = document.getElementById("sumulas");
|
||||
limpar(alvo);
|
||||
if (!sumulas || !sumulas.length) { alvo.appendChild(el("p", "explica", "As súmulas dos jogos do seu time aparecem aqui.")); return; }
|
||||
sumulas.forEach(function (s) {
|
||||
var j = (jogos || []).filter(function (x) { return x.ref === s.jogoRef; })[0];
|
||||
var card = el("div", "sumula");
|
||||
var cab = j ? j.mandante + " x " + j.visitante : "Jogo " + s.jogoRef;
|
||||
if (j && j.placarMandante !== undefined && j.placarMandante !== null) {
|
||||
cab = j.mandante + " " + j.placarMandante + " x " + j.placarVisitante + " " + j.visitante;
|
||||
}
|
||||
card.appendChild(el("h3", null, cab));
|
||||
if (j) card.appendChild(el("div", "quando", (j.rodada ? j.rodada + " · " : "") + dataBr(j.data)));
|
||||
secaoDaSumula(card, "Gols", s.gols, function (g) { return momento(g) + " · " + g.time + " · " + pessoa(g.jogador); });
|
||||
secaoDaSumula(card, "Cartões", s.cartoes, function (c) {
|
||||
return momento(c) + " · " + (c.tipo === "VERMELHO" ? "vermelho" : "amarelo") + " · " + c.time + " · " + pessoa(c.jogador);
|
||||
});
|
||||
secaoDaSumula(card, "Substituições", s.substituicoes, function (t) {
|
||||
return momento(t) + " · " + t.time + " · sai " + pessoa(t.sai) + ", entra " + pessoa(t.entra);
|
||||
});
|
||||
alvo.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ ficha
|
||||
|
||||
function linhaDeJogador(j) {
|
||||
var linha = el("div", "linha-ficha linha-jogador");
|
||||
if (j && j.id) linha.setAttribute("data-id", j.id);
|
||||
var nome = el("input");
|
||||
nome.placeholder = "Nome do jogador";
|
||||
nome.maxLength = 120;
|
||||
nome.value = (j && j.nome) || "";
|
||||
nome.setAttribute("aria-label", "Nome do jogador");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
var camisa = el("input");
|
||||
camisa.type = "number";
|
||||
camisa.min = "0";
|
||||
camisa.max = "999";
|
||||
camisa.placeholder = "nº";
|
||||
camisa.value = j && j.numeroCamisa !== undefined && j.numeroCamisa !== null ? j.numeroCamisa : "";
|
||||
camisa.setAttribute("aria-label", "Número da camisa");
|
||||
camisa.setAttribute("data-campo", "camisa");
|
||||
var tirar = el("button", "secundario", "×");
|
||||
tirar.type = "button";
|
||||
tirar.setAttribute("aria-label", "Remover jogador");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(camisa);
|
||||
linha.appendChild(tirar);
|
||||
listaJogadores.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
function linhaDeComissao(m) {
|
||||
var linha = el("div", "linha-ficha linha-comissao");
|
||||
if (m && m.id) linha.setAttribute("data-id", m.id);
|
||||
var nome = el("input");
|
||||
nome.placeholder = "Nome";
|
||||
nome.maxLength = 120;
|
||||
nome.value = (m && m.nome) || "";
|
||||
nome.setAttribute("aria-label", "Nome na comissão técnica");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
var papel = el("input");
|
||||
papel.placeholder = "Função";
|
||||
papel.maxLength = 40;
|
||||
papel.value = (m && m.funcao) || "";
|
||||
papel.setAttribute("list", "funcoes-comissao");
|
||||
papel.setAttribute("aria-label", "Função na comissão técnica");
|
||||
papel.setAttribute("data-campo", "funcao");
|
||||
var tirar = el("button", "secundario", "×");
|
||||
tirar.type = "button";
|
||||
tirar.setAttribute("aria-label", "Remover da comissão");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(papel);
|
||||
linha.appendChild(tirar);
|
||||
listaComissao.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
/** Time da escola ou ficha fechada: só leitura (listas simples). */
|
||||
function fichaSomenteLeitura(ficha) {
|
||||
formFicha.hidden = true;
|
||||
var alvo = document.getElementById("ficha-leitura");
|
||||
if (!alvo) {
|
||||
alvo = el("div", "somente-leitura");
|
||||
alvo.id = "ficha-leitura";
|
||||
formFicha.parentNode.appendChild(alvo);
|
||||
}
|
||||
limpar(alvo);
|
||||
alvo.appendChild(el("h3", null, "Jogadores"));
|
||||
var ul = el("ul");
|
||||
(ficha.jogadores || []).forEach(function (j) {
|
||||
ul.appendChild(el("li", null, pessoa({ nome: j.nome, numeroCamisa: j.numeroCamisa })));
|
||||
});
|
||||
if (!ul.childNodes.length) ul.appendChild(el("li", null, "Nenhum jogador na ficha."));
|
||||
alvo.appendChild(ul);
|
||||
if ((ficha.comissao || []).length) {
|
||||
alvo.appendChild(el("h3", null, "Comissão técnica"));
|
||||
var uc = el("ul");
|
||||
ficha.comissao.forEach(function (m) { uc.appendChild(el("li", null, m.nome + " — " + m.funcao)); });
|
||||
alvo.appendChild(uc);
|
||||
}
|
||||
}
|
||||
|
||||
function montarFicha(ficha) {
|
||||
var aviso = document.getElementById("ficha-aviso");
|
||||
limpar(listaJogadores);
|
||||
limpar(listaComissao);
|
||||
if (!ficha.editavel) {
|
||||
aviso.textContent = "Esta ficha está fechada para alterações aqui. Se algo estiver errado, fale com a organização.";
|
||||
fichaSomenteLeitura(ficha);
|
||||
return;
|
||||
}
|
||||
aviso.textContent = "Confira os jogadores (nome e número da camisa) e a comissão técnica. Dá para corrigir até o primeiro jogo ser encerrado.";
|
||||
formFicha.hidden = false;
|
||||
(ficha.jogadores || []).forEach(linhaDeJogador);
|
||||
if (!(ficha.jogadores || []).length) for (var i = 0; i < 3; i++) linhaDeJogador();
|
||||
(ficha.comissao || []).forEach(linhaDeComissao);
|
||||
}
|
||||
|
||||
function mostrarErroFicha(texto) {
|
||||
fichaErro.textContent = texto || "";
|
||||
fichaErro.hidden = !texto;
|
||||
}
|
||||
|
||||
function mostrarOkFicha(texto) {
|
||||
fichaOk.textContent = texto || "";
|
||||
fichaOk.hidden = !texto;
|
||||
}
|
||||
|
||||
function corpoDaFicha() {
|
||||
var jogadores = [];
|
||||
var linhas = listaJogadores.querySelectorAll(".linha-jogador");
|
||||
for (var i = 0; i < linhas.length; i++) {
|
||||
var nome = linhas[i].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!nome) continue;
|
||||
var c = linhas[i].querySelector('[data-campo="camisa"]').value;
|
||||
var j = { nome: nome, numeroCamisa: c === "" ? null : parseInt(c, 10) };
|
||||
if (linhas[i].getAttribute("data-id")) j.id = linhas[i].getAttribute("data-id");
|
||||
jogadores.push(j);
|
||||
}
|
||||
var comissao = [];
|
||||
var faltaFuncao = false;
|
||||
var pessoas = listaComissao.querySelectorAll(".linha-comissao");
|
||||
for (var k = 0; k < pessoas.length; k++) {
|
||||
var n = pessoas[k].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!n) continue;
|
||||
var f = pessoas[k].querySelector('[data-campo="funcao"]').value.trim();
|
||||
if (!f) faltaFuncao = true;
|
||||
var m = { nome: n, funcao: f };
|
||||
if (pessoas[k].getAttribute("data-id")) m.id = pessoas[k].getAttribute("data-id");
|
||||
comissao.push(m);
|
||||
}
|
||||
return { jogadores: jogadores, comissao: comissao, faltaFuncao: faltaFuncao };
|
||||
}
|
||||
|
||||
function aoSalvar(ev) {
|
||||
ev.preventDefault();
|
||||
mostrarErroFicha("");
|
||||
mostrarOkFicha("");
|
||||
var c = corpoDaFicha();
|
||||
if (c.jogadores.length > MAX_JOGADORES) { mostrarErroFicha("No máximo " + MAX_JOGADORES + " jogadores."); return; }
|
||||
if (c.faltaFuncao) { mostrarErroFicha("Informe a função de cada pessoa da comissão técnica."); return; }
|
||||
if (c.comissao.length > MAX_COMISSAO) { mostrarErroFicha("No máximo " + MAX_COMISSAO + " pessoas na comissão técnica."); return; }
|
||||
salvar.disabled = true;
|
||||
salvar.setAttribute("aria-busy", "true");
|
||||
fetch(base + "/ficha", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ jogadores: c.jogadores, comissao: c.comissao })
|
||||
}).then(lerResposta).then(function (r) {
|
||||
salvar.disabled = false;
|
||||
salvar.removeAttribute("aria-busy");
|
||||
if (r.status >= 200 && r.status < 300) {
|
||||
mostrarOkFicha("Ficha salva.");
|
||||
return carregar(true);
|
||||
}
|
||||
if (r.status === 404) { recusar("Este link não está mais disponível."); return null; }
|
||||
mostrarErroFicha((r.corpo && r.corpo.detail) || "Não foi possível salvar agora. Tente de novo em instantes.");
|
||||
return null;
|
||||
}).catch(function () {
|
||||
salvar.disabled = false;
|
||||
salvar.removeAttribute("aria-busy");
|
||||
mostrarErroFicha("Sem conexão. Confira a internet e tente de novo.");
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ carga
|
||||
|
||||
function mostrar(v, manterAba) {
|
||||
visao = v;
|
||||
var nomeTime = (v.time && v.time.nome) || "Seu time";
|
||||
titulo.textContent = (v.competicao || "Competição") + " — " + nomeTime;
|
||||
document.title = titulo.textContent + " — Athletic Map";
|
||||
var partes = [];
|
||||
if (v.time && v.time.escola) partes.push(v.time.escola);
|
||||
if (v.time && v.time.cidade) partes.push(v.time.cidade);
|
||||
if (v.temporada && v.temporada.nome) partes.push("Temporada " + v.temporada.nome);
|
||||
if (v.time && v.time.status === "DESQUALIFICADO") partes.push("Time desqualificado");
|
||||
situacao.textContent = partes.join(" · ");
|
||||
situacao.className = "";
|
||||
painel.hidden = false;
|
||||
montarTabela(v.classificacao, nomeTime);
|
||||
montarJogos(v.jogos);
|
||||
montarSumulas(v.sumulas, v.jogos);
|
||||
montarFicha(v.ficha || { jogadores: [], comissao: [], editavel: false });
|
||||
if (!manterAba) abrirAba("tabela", false);
|
||||
}
|
||||
|
||||
function carregar(manterAba) {
|
||||
return fetch(base).then(lerResposta).then(function (r) {
|
||||
if (r.status === 404) { recusar("Este link não está disponível ou foi desativado. Peça um novo à organização."); return; }
|
||||
if (r.status === 429) { recusar("Muitas consultas seguidas. Aguarde alguns minutos e abra o link de novo."); return; }
|
||||
if (r.status !== 200) { recusar("Não foi possível abrir agora. Tente de novo em instantes."); return; }
|
||||
mostrar(r.corpo || {}, manterAba);
|
||||
}).catch(function () {
|
||||
recusar("Não foi possível abrir agora. Tente de novo em instantes.");
|
||||
});
|
||||
}
|
||||
|
||||
function iniciar() {
|
||||
if (!TOKEN.test(token)) { recusar("Este endereço não é válido."); return; }
|
||||
ABAS.forEach(function (nome) {
|
||||
var aba = document.getElementById("aba-" + nome);
|
||||
aba.addEventListener("click", function () { abrirAba(nome, false); });
|
||||
aba.addEventListener("keydown", aoTeclarNaAba);
|
||||
});
|
||||
document.getElementById("maisJogador").addEventListener("click", function () {
|
||||
if (listaJogadores.querySelectorAll(".linha-jogador").length < MAX_JOGADORES) linhaDeJogador().querySelector("input").focus();
|
||||
});
|
||||
document.getElementById("maisComissao").addEventListener("click", function () {
|
||||
if (listaComissao.querySelectorAll(".linha-comissao").length < MAX_COMISSAO) linhaDeComissao(null).querySelector("input").focus();
|
||||
});
|
||||
formFicha.addEventListener("submit", aoSalvar);
|
||||
carregar(false);
|
||||
}
|
||||
|
||||
iniciar();
|
||||
})();
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
|
||||
@@ -162,6 +162,9 @@ spec:
|
||||
- { key: "preparando.js", path: "preparando.js" }
|
||||
- { key: "privacidade.html", path: "privacidade.html" }
|
||||
- { key: "termos.html", path: "termos.html" }
|
||||
- { key: "time.css", path: "time.css" }
|
||||
- { key: "time.html", path: "time.html" }
|
||||
- { key: "time.js", path: "time.js" }
|
||||
- configMap:
|
||||
name: frontend-publico-binario
|
||||
items:
|
||||
|
||||
@@ -409,7 +409,7 @@ data:
|
||||
|
||||
<form id="formulario" hidden novalidate>
|
||||
<p class="explica">
|
||||
Preencha os dados do seu time e dos jogadores (só o nome e o número da camisa).
|
||||
Preencha os dados do seu time, dos jogadores (só o nome e o número da camisa) e da comissão técnica (nome e função).
|
||||
A organização confere e aprova antes do time entrar na competição.
|
||||
</p>
|
||||
|
||||
@@ -433,12 +433,23 @@ data:
|
||||
<button type="button" id="maisJogador" class="secundario">+ Jogador</button>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Comissão técnica</legend>
|
||||
<div id="comissao"></div>
|
||||
<datalist id="funcoes-comissao">
|
||||
<option value="Técnico"></option><option value="Auxiliar técnico"></option><option value="Preparador físico"></option>
|
||||
<option value="Preparador de goleiros"></option><option value="Médico"></option><option value="Fisioterapeuta"></option>
|
||||
<option value="Massagista"></option><option value="Roupeiro"></option><option value="Dirigente"></option>
|
||||
</datalist>
|
||||
<button type="button" id="maisComissao" class="secundario">+ Pessoa na comissão</button>
|
||||
</fieldset>
|
||||
|
||||
<p id="erro" class="erro" role="alert" hidden></p>
|
||||
<button type="submit" id="enviar">Enviar time</button>
|
||||
</form>
|
||||
</main>
|
||||
<script src="/publico/desafio.js?v=1"></script>
|
||||
<script src="/publico/convite.js?v=2"></script>
|
||||
<script src="/publico/convite.js?v=3"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -465,6 +476,7 @@ data:
|
||||
|
||||
var TOKEN = /^[A-Za-z0-9_-]{20,64}$/;
|
||||
var MAX_JOGADORES = 60;
|
||||
var MAX_COMISSAO = 15;
|
||||
|
||||
var token = decodeURIComponent((location.pathname.split("/").filter(Boolean).pop() || ""));
|
||||
var base = "/api/public/competicoes/convites/" + encodeURIComponent(token);
|
||||
@@ -473,6 +485,7 @@ data:
|
||||
var situacao = document.getElementById("situacao");
|
||||
var formulario = document.getElementById("formulario");
|
||||
var listaJogadores = document.getElementById("jogadores");
|
||||
var listaComissao = document.getElementById("comissao");
|
||||
var erro = document.getElementById("erro");
|
||||
var enviar = document.getElementById("enviar");
|
||||
var ROTULO_ENVIAR = enviar.textContent;
|
||||
@@ -520,6 +533,39 @@ data:
|
||||
return linha;
|
||||
}
|
||||
|
||||
/** Comissao tecnica (21/10/2026): nome e funcao; a funcao tem sugestoes, mas aceita texto livre. */
|
||||
function linhaDeComissao(funcao) {
|
||||
var linha = document.createElement("div");
|
||||
linha.className = "linha-jogador linha-comissao";
|
||||
|
||||
var nome = document.createElement("input");
|
||||
nome.placeholder = "Nome";
|
||||
nome.maxLength = 120;
|
||||
nome.setAttribute("aria-label", "Nome na comissão técnica");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
|
||||
var papel = document.createElement("input");
|
||||
papel.placeholder = "Função";
|
||||
papel.maxLength = 40;
|
||||
papel.value = funcao || "";
|
||||
papel.setAttribute("list", "funcoes-comissao");
|
||||
papel.setAttribute("aria-label", "Função na comissão técnica");
|
||||
papel.setAttribute("data-campo", "funcao");
|
||||
|
||||
var tirar = document.createElement("button");
|
||||
tirar.type = "button";
|
||||
tirar.className = "secundario";
|
||||
tirar.textContent = "×";
|
||||
tirar.setAttribute("aria-label", "Remover da comissão");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(papel);
|
||||
linha.appendChild(tirar);
|
||||
listaComissao.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
function valor(id) {
|
||||
var v = document.getElementById(id).value.trim();
|
||||
return v === "" ? null : v;
|
||||
@@ -534,14 +580,26 @@ data:
|
||||
var c = linhas[i].querySelector('[data-campo="camisa"]').value;
|
||||
jogadores.push({ nome: nome, numeroCamisa: c === "" ? null : parseInt(c, 10) });
|
||||
}
|
||||
var comissao = [];
|
||||
var faltaFuncao = false;
|
||||
var pessoas = listaComissao.querySelectorAll(".linha-comissao");
|
||||
for (var k = 0; k < pessoas.length; k++) {
|
||||
var n = pessoas[k].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!n) continue;
|
||||
var f = pessoas[k].querySelector('[data-campo="funcao"]').value.trim();
|
||||
if (!f) faltaFuncao = true;
|
||||
comissao.push({ nome: n, funcao: f });
|
||||
}
|
||||
return {
|
||||
faltaFuncao: faltaFuncao,
|
||||
nome: valor("nome"),
|
||||
escola: valor("escola"),
|
||||
cidade: valor("cidade"),
|
||||
contatoNome: valor("contatoNome"),
|
||||
contatoTelefone: valor("contatoTelefone"),
|
||||
contatoEmail: valor("contatoEmail"),
|
||||
jogadores: jogadores
|
||||
jogadores: jogadores,
|
||||
comissao: comissao
|
||||
};
|
||||
}
|
||||
|
||||
@@ -555,6 +613,9 @@ data:
|
||||
var corpo = montarCorpo();
|
||||
if (!corpo.nome) { mostrarErro("Informe o nome do time."); return; }
|
||||
if (corpo.jogadores.length > MAX_JOGADORES) { mostrarErro("No máximo " + MAX_JOGADORES + " jogadores."); return; }
|
||||
if (corpo.faltaFuncao) { mostrarErro("Informe a função de cada pessoa da comissão técnica."); return; }
|
||||
if (corpo.comissao.length > MAX_COMISSAO) { mostrarErro("No máximo " + MAX_COMISSAO + " pessoas na comissão técnica."); return; }
|
||||
delete corpo.faltaFuncao;
|
||||
enviar.disabled = true;
|
||||
enviar.textContent = "Verificando…";
|
||||
enviar.setAttribute("aria-busy", "true");
|
||||
@@ -615,6 +676,7 @@ data:
|
||||
situacao.className = "";
|
||||
formulario.hidden = false;
|
||||
for (var i = 0; i < 5; i++) linhaDeJogador();
|
||||
linhaDeComissao("Técnico");
|
||||
}).catch(function () {
|
||||
recusar("Não foi possível abrir o convite agora. Tente de novo em instantes.");
|
||||
});
|
||||
@@ -623,6 +685,9 @@ data:
|
||||
document.getElementById("maisJogador").addEventListener("click", function () {
|
||||
if (listaJogadores.querySelectorAll(".linha-jogador").length < MAX_JOGADORES) linhaDeJogador().querySelector("input").focus();
|
||||
});
|
||||
document.getElementById("maisComissao").addEventListener("click", function () {
|
||||
if (listaComissao.querySelectorAll(".linha-comissao").length < MAX_COMISSAO) linhaDeComissao("").querySelector("input").focus();
|
||||
});
|
||||
formulario.addEventListener("submit", aoEnviar);
|
||||
iniciar();
|
||||
})();
|
||||
@@ -8242,6 +8307,557 @@ data:
|
||||
</body>
|
||||
</html>
|
||||
|
||||
time.css: |
|
||||
/* Link do time: página pública, sem fonte nem script de fora (a CSP só deixa 'self'). Mobile primeiro. */
|
||||
main { max-width: 56rem; }
|
||||
.explica, .legenda { color: var(--muted); font-size: .9rem; }
|
||||
.abas { display: flex; gap: .25rem; overflow-x: auto; border-bottom: 2px solid var(--line); margin: 0 0 1rem; }
|
||||
.abas button { background: transparent; color: var(--navy); border: 0; border-bottom: 3px solid transparent; border-radius: 0; padding: .6rem .9rem; white-space: nowrap; font: inherit; font-weight: 700; cursor: pointer; }
|
||||
.abas button[aria-selected="true"] { border-bottom-color: var(--orange); }
|
||||
.abas button:focus-visible, button:focus-visible, input:focus-visible { outline: 3px solid var(--orange); outline-offset: 1px; }
|
||||
h2 { font-size: 1.15rem; margin: .5rem 0; }
|
||||
.rolagem { overflow-x: auto; }
|
||||
table { border-collapse: collapse; width: 100%; font-size: .9rem; background: #fff; }
|
||||
th, td { padding: .4rem .45rem; text-align: center; border-bottom: 1px solid var(--line); white-space: nowrap; }
|
||||
th:nth-child(2), td:nth-child(2) { text-align: left; white-space: normal; min-width: 8rem; }
|
||||
tr.meu { background: #fff4e5; font-weight: 700; }
|
||||
.forma { display: inline-flex; gap: 3px; vertical-align: middle; }
|
||||
.ponto { width: .8rem; height: .8rem; border-radius: 50%; display: inline-block; }
|
||||
.ponto.V { background: #067647; }
|
||||
.ponto.E { background: #98a2b3; }
|
||||
.ponto.D { background: #b42318; }
|
||||
.cartao { display: inline-block; width: .7rem; height: .95rem; border-radius: 2px; vertical-align: middle; margin-right: 3px; }
|
||||
.cartao.amarelo { background: #f5c518; }
|
||||
.cartao.vermelho { background: #d92d20; }
|
||||
.jogo, .sumula { background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: .6rem .8rem; margin: 0 0 .6rem; }
|
||||
.jogo .quando { color: var(--muted); font-size: .85rem; }
|
||||
.jogo .confronto { display: grid; grid-template-columns: 1fr auto 1fr; gap: .5rem; align-items: center; font-weight: 700; }
|
||||
.jogo .confronto span:first-child { text-align: right; }
|
||||
.jogo .placar { background: var(--navy); color: #fff; border-radius: 6px; padding: .1rem .6rem; }
|
||||
.etiqueta { display: inline-block; font-size: .75rem; font-weight: 700; padding: .05rem .5rem; border-radius: 99px; background: #fff1e3; border: 1px solid var(--orange); margin-left: .4rem; }
|
||||
.sumula h3 { margin: 0 0 .3rem; font-size: 1rem; }
|
||||
.sumula ul { margin: .2rem 0 .5rem; padding-left: 1.2rem; }
|
||||
form { display: grid; gap: 1rem; }
|
||||
fieldset { border: 1px solid var(--line); border-radius: 8px; background: #fff; padding: .75rem 1rem 1rem; display: grid; gap: .6rem; }
|
||||
legend { font-weight: 700; padding: 0 .4rem; }
|
||||
input { font: inherit; padding: .5rem .6rem; border: 1px solid var(--line); border-radius: 6px; width: 100%; }
|
||||
.linha-ficha { display: grid; grid-template-columns: 1fr 5.5rem auto; gap: .4rem; align-items: center; }
|
||||
.linha-ficha button { padding: .4rem .6rem; }
|
||||
button { font: inherit; font-weight: 700; padding: .65rem 1.1rem; border: 0; border-radius: 8px; background: var(--orange); color: #fff; cursor: pointer; }
|
||||
button.secundario { background: #fff; color: var(--navy); border: 1px solid var(--line); justify-self: start; }
|
||||
button:disabled { opacity: .55; cursor: not-allowed; }
|
||||
.erro { color: #b42318; margin: 0; font-weight: 600; }
|
||||
.ok { background: #e7f6ec; border-left: 4px solid #067647; padding: .8rem 1rem; margin: 0; }
|
||||
#situacao.invalido { background: #fff1e3; border-left: 4px solid var(--orange); padding: 1rem 1.2rem; }
|
||||
.somente-leitura li { margin: .15rem 0; }
|
||||
@media (max-width: 30rem) { .linha-ficha { grid-template-columns: 1fr 4.5rem auto; } }
|
||||
|
||||
time.html: |
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<title>Seu time na competição — Athletic Map</title>
|
||||
<link rel="stylesheet" href="/publico/_estilo.css">
|
||||
<link rel="stylesheet" href="/publico/time.css?v=1">
|
||||
</head>
|
||||
<body>
|
||||
<header><div class="marca">Athletic <span>Map</span></div></header>
|
||||
<main>
|
||||
<h1 id="titulo">Seu time na competição</h1>
|
||||
<p id="situacao" role="status" aria-live="polite">Abrindo o link…</p>
|
||||
|
||||
<div id="painel" hidden>
|
||||
<div class="abas" role="tablist" aria-label="Seções">
|
||||
<button type="button" role="tab" id="aba-tabela" aria-controls="sec-tabela" aria-selected="true" data-aba="tabela">Tabela</button>
|
||||
<button type="button" role="tab" id="aba-jogos" aria-controls="sec-jogos" aria-selected="false" tabindex="-1" data-aba="jogos">Jogos</button>
|
||||
<button type="button" role="tab" id="aba-sumulas" aria-controls="sec-sumulas" aria-selected="false" tabindex="-1" data-aba="sumulas">Súmulas</button>
|
||||
<button type="button" role="tab" id="aba-ficha" aria-controls="sec-ficha" aria-selected="false" tabindex="-1" data-aba="ficha">Ficha do time</button>
|
||||
</div>
|
||||
|
||||
<section id="sec-tabela" role="tabpanel" aria-labelledby="aba-tabela">
|
||||
<h2>Tabela</h2>
|
||||
<div class="rolagem" id="tabela"></div>
|
||||
<p class="legenda">P pontos · J jogos · V vitórias · E empates · D derrotas · GP gols pró · GC gols contra · SG saldo de gols · % aproveitamento · CA cartões amarelos · CV cartões vermelhos</p>
|
||||
</section>
|
||||
|
||||
<section id="sec-jogos" role="tabpanel" aria-labelledby="aba-jogos" hidden>
|
||||
<h2>Jogos</h2>
|
||||
<div id="jogos"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-sumulas" role="tabpanel" aria-labelledby="aba-sumulas" hidden>
|
||||
<h2>Súmulas</h2>
|
||||
<div id="sumulas"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-ficha" role="tabpanel" aria-labelledby="aba-ficha" hidden>
|
||||
<h2>Ficha do time</h2>
|
||||
<p id="ficha-aviso" class="explica"></p>
|
||||
<form id="ficha" novalidate>
|
||||
<fieldset>
|
||||
<legend>Jogadores</legend>
|
||||
<div id="jogadores"></div>
|
||||
<button type="button" id="maisJogador" class="secundario">+ Jogador</button>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Comissão técnica</legend>
|
||||
<div id="comissao"></div>
|
||||
<datalist id="funcoes-comissao">
|
||||
<option value="Técnico"></option><option value="Auxiliar técnico"></option><option value="Preparador físico"></option>
|
||||
<option value="Preparador de goleiros"></option><option value="Médico"></option><option value="Fisioterapeuta"></option>
|
||||
<option value="Massagista"></option><option value="Roupeiro"></option><option value="Dirigente"></option>
|
||||
</datalist>
|
||||
<button type="button" id="maisComissao" class="secundario">+ Pessoa na comissão</button>
|
||||
</fieldset>
|
||||
<p id="ficha-erro" class="erro" role="alert" hidden></p>
|
||||
<p id="ficha-ok" class="ok" role="status" hidden></p>
|
||||
<button type="submit" id="salvar">Salvar ficha</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<script src="/publico/time.js?v=1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
time.js: |
|
||||
/* ===========================================================================
|
||||
* Link do time — a página que UMA pessoa do time abre pelo link com chave.
|
||||
*
|
||||
* Sem login: a chave no endereço é o único segredo. A página mostra a tabela,
|
||||
* os jogos e as súmulas da competição e a ficha do time; se o time for de
|
||||
* fora e ainda não houver jogo encerrado, a ficha pode ser corrigida aqui.
|
||||
*
|
||||
* Tudo o que vem do servidor entra na página com `textContent` — nunca como
|
||||
* HTML. O script é externo porque a CSP é `script-src 'self'`.
|
||||
* =========================================================================== */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var TOKEN = /^[A-Za-z0-9_-]{20,64}$/;
|
||||
var MAX_JOGADORES = 60;
|
||||
var MAX_COMISSAO = 15;
|
||||
var ABAS = ["tabela", "jogos", "sumulas", "ficha"];
|
||||
var ROTULOS_FORMA = { V: "Vitória", E: "Empate", D: "Derrota" };
|
||||
var ROTULOS_STATUS = {
|
||||
AGENDADA: "Agendado", ADIADA: "Adiado", EM_ANDAMENTO: "Em andamento", ENCERRADA: "Encerrado", CANCELADA: "Cancelado"
|
||||
};
|
||||
|
||||
var token = decodeURIComponent(location.pathname.split("/").filter(Boolean).pop() || "");
|
||||
var base = "/api/public/competicoes/times/" + encodeURIComponent(token);
|
||||
|
||||
var titulo = document.getElementById("titulo");
|
||||
var situacao = document.getElementById("situacao");
|
||||
var painel = document.getElementById("painel");
|
||||
var listaJogadores = document.getElementById("jogadores");
|
||||
var listaComissao = document.getElementById("comissao");
|
||||
var formFicha = document.getElementById("ficha");
|
||||
var fichaErro = document.getElementById("ficha-erro");
|
||||
var fichaOk = document.getElementById("ficha-ok");
|
||||
var salvar = document.getElementById("salvar");
|
||||
var visao = null;
|
||||
|
||||
function el(tag, classe, texto) {
|
||||
var e = document.createElement(tag);
|
||||
if (classe) e.className = classe;
|
||||
if (texto !== undefined && texto !== null) e.textContent = String(texto);
|
||||
return e;
|
||||
}
|
||||
|
||||
function limpar(no) {
|
||||
while (no.firstChild) no.removeChild(no.firstChild);
|
||||
}
|
||||
|
||||
function recusar(texto) {
|
||||
situacao.textContent = texto;
|
||||
situacao.className = "invalido";
|
||||
painel.hidden = true;
|
||||
}
|
||||
|
||||
function lerResposta(r) {
|
||||
return r.json().catch(function () { return {}; }).then(function (corpo) { return { status: r.status, corpo: corpo }; });
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ abas
|
||||
|
||||
function abrirAba(nome, focar) {
|
||||
for (var i = 0; i < ABAS.length; i++) {
|
||||
var ativa = ABAS[i] === nome;
|
||||
var aba = document.getElementById("aba-" + ABAS[i]);
|
||||
aba.setAttribute("aria-selected", ativa ? "true" : "false");
|
||||
aba.tabIndex = ativa ? 0 : -1;
|
||||
document.getElementById("sec-" + ABAS[i]).hidden = !ativa;
|
||||
if (ativa && focar) aba.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function aoTeclarNaAba(ev) {
|
||||
var i = ABAS.indexOf(ev.currentTarget.getAttribute("data-aba"));
|
||||
if (ev.key === "ArrowRight") { abrirAba(ABAS[(i + 1) % ABAS.length], true); ev.preventDefault(); }
|
||||
else if (ev.key === "ArrowLeft") { abrirAba(ABAS[(i + ABAS.length - 1) % ABAS.length], true); ev.preventDefault(); }
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ tabela
|
||||
|
||||
function dataBr(iso) {
|
||||
if (!iso) return "Data a definir";
|
||||
var d = new Date(iso);
|
||||
if (isNaN(d.getTime())) return "Data a definir";
|
||||
try {
|
||||
return d.toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" });
|
||||
} catch (e) {
|
||||
return d.toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
function percentual(n) {
|
||||
return (Math.round((n || 0) * 10) / 10).toString().replace(".", ",") + "%";
|
||||
}
|
||||
|
||||
function montarTabela(linhas, meuNome) {
|
||||
var alvo = document.getElementById("tabela");
|
||||
limpar(alvo);
|
||||
if (!linhas || !linhas.length) { alvo.appendChild(el("p", "explica", "A tabela aparece quando houver times e jogos.")); return; }
|
||||
var t = el("table");
|
||||
t.appendChild(el("caption", "explica", "Classificação"));
|
||||
var cab = el("tr");
|
||||
var cols = [["#", "Posição"], ["Time", "Time"], ["P", "Pontos"], ["J", "Jogos"], ["V", "Vitórias"], ["E", "Empates"],
|
||||
["D", "Derrotas"], ["GP", "Gols pró"], ["GC", "Gols contra"], ["SG", "Saldo de gols"], ["%", "Aproveitamento"],
|
||||
["Últimos 5", "Últimos 5 jogos"], ["CA", "Cartões amarelos"], ["CV", "Cartões vermelhos"]];
|
||||
cols.forEach(function (c) {
|
||||
var th = el("th", null, c[0]);
|
||||
th.scope = "col";
|
||||
th.title = c[1];
|
||||
cab.appendChild(th);
|
||||
});
|
||||
var thead = el("thead");
|
||||
thead.appendChild(cab);
|
||||
t.appendChild(thead);
|
||||
var corpo = el("tbody");
|
||||
linhas.forEach(function (l) {
|
||||
var tr = el("tr", l.time === meuNome ? "meu" : "");
|
||||
[l.posicao, l.time, l.pontos, l.jogos, l.vitorias, l.empates, l.derrotas, l.golsPro, l.golsContra, l.saldoGols,
|
||||
percentual(l.aproveitamento)].forEach(function (v) { tr.appendChild(el("td", null, v)); });
|
||||
var forma = el("td");
|
||||
var caixa = el("span", "forma");
|
||||
(l.ultimos || []).forEach(function (r) {
|
||||
var p = el("span", "ponto " + r);
|
||||
p.title = ROTULOS_FORMA[r] || r;
|
||||
p.setAttribute("role", "img");
|
||||
p.setAttribute("aria-label", ROTULOS_FORMA[r] || r);
|
||||
caixa.appendChild(p);
|
||||
});
|
||||
forma.appendChild(caixa);
|
||||
tr.appendChild(forma);
|
||||
var am = el("td");
|
||||
am.appendChild(el("span", "cartao amarelo"));
|
||||
am.appendChild(document.createTextNode(String(l.amarelos || 0)));
|
||||
tr.appendChild(am);
|
||||
var vm = el("td");
|
||||
vm.appendChild(el("span", "cartao vermelho"));
|
||||
vm.appendChild(document.createTextNode(String(l.vermelhos || 0)));
|
||||
tr.appendChild(vm);
|
||||
corpo.appendChild(tr);
|
||||
});
|
||||
t.appendChild(corpo);
|
||||
alvo.appendChild(t);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ jogos
|
||||
|
||||
function montarJogos(jogos) {
|
||||
var alvo = document.getElementById("jogos");
|
||||
limpar(alvo);
|
||||
if (!jogos || !jogos.length) { alvo.appendChild(el("p", "explica", "Ainda não há jogos marcados.")); return; }
|
||||
jogos.forEach(function (j) {
|
||||
var card = el("div", "jogo");
|
||||
var quando = el("div", "quando", (j.rodada ? j.rodada + " · " : "") + dataBr(j.data));
|
||||
var etiqueta = el("span", "etiqueta", ROTULOS_STATUS[j.status] || j.status);
|
||||
quando.appendChild(etiqueta);
|
||||
if (j.wo) quando.appendChild(el("span", "etiqueta", "W.O."));
|
||||
card.appendChild(quando);
|
||||
var linha = el("div", "confronto");
|
||||
linha.appendChild(el("span", null, j.mandante));
|
||||
var temPlacar = j.placarMandante !== undefined && j.placarMandante !== null;
|
||||
linha.appendChild(el("span", temPlacar ? "placar" : "", temPlacar ? j.placarMandante + " x " + j.placarVisitante : "x"));
|
||||
linha.appendChild(el("span", null, j.visitante));
|
||||
card.appendChild(linha);
|
||||
if (j.penaltis) card.appendChild(el("div", "quando", "Pênaltis: " + j.penaltis.mandante + " x " + j.penaltis.visitante));
|
||||
if (j.motivo) card.appendChild(el("div", "quando", "Motivo: " + j.motivo));
|
||||
alvo.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ súmulas
|
||||
|
||||
function pessoa(p) {
|
||||
if (!p) return "jogador não identificado";
|
||||
if (p.nome && p.numeroCamisa !== undefined && p.numeroCamisa !== null) return p.nome + " (" + p.numeroCamisa + ")";
|
||||
if (p.nome) return p.nome;
|
||||
if (p.numeroCamisa !== undefined && p.numeroCamisa !== null) return "camisa " + p.numeroCamisa;
|
||||
return "jogador não identificado";
|
||||
}
|
||||
|
||||
function momento(e) {
|
||||
var m = e.minuto !== undefined && e.minuto !== null ? e.minuto + "'" : "";
|
||||
var t = e.tempo ? e.tempo + "º tempo" : "";
|
||||
return [t, m].filter(Boolean).join(" ") || "—";
|
||||
}
|
||||
|
||||
function secaoDaSumula(card, nome, itens, texto) {
|
||||
if (!itens || !itens.length) return;
|
||||
card.appendChild(el("strong", null, nome));
|
||||
var ul = el("ul");
|
||||
itens.forEach(function (i) { ul.appendChild(el("li", null, texto(i))); });
|
||||
card.appendChild(ul);
|
||||
}
|
||||
|
||||
function montarSumulas(sumulas, jogos) {
|
||||
var alvo = document.getElementById("sumulas");
|
||||
limpar(alvo);
|
||||
if (!sumulas || !sumulas.length) { alvo.appendChild(el("p", "explica", "As súmulas dos jogos do seu time aparecem aqui.")); return; }
|
||||
sumulas.forEach(function (s) {
|
||||
var j = (jogos || []).filter(function (x) { return x.ref === s.jogoRef; })[0];
|
||||
var card = el("div", "sumula");
|
||||
var cab = j ? j.mandante + " x " + j.visitante : "Jogo " + s.jogoRef;
|
||||
if (j && j.placarMandante !== undefined && j.placarMandante !== null) {
|
||||
cab = j.mandante + " " + j.placarMandante + " x " + j.placarVisitante + " " + j.visitante;
|
||||
}
|
||||
card.appendChild(el("h3", null, cab));
|
||||
if (j) card.appendChild(el("div", "quando", (j.rodada ? j.rodada + " · " : "") + dataBr(j.data)));
|
||||
secaoDaSumula(card, "Gols", s.gols, function (g) { return momento(g) + " · " + g.time + " · " + pessoa(g.jogador); });
|
||||
secaoDaSumula(card, "Cartões", s.cartoes, function (c) {
|
||||
return momento(c) + " · " + (c.tipo === "VERMELHO" ? "vermelho" : "amarelo") + " · " + c.time + " · " + pessoa(c.jogador);
|
||||
});
|
||||
secaoDaSumula(card, "Substituições", s.substituicoes, function (t) {
|
||||
return momento(t) + " · " + t.time + " · sai " + pessoa(t.sai) + ", entra " + pessoa(t.entra);
|
||||
});
|
||||
alvo.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ ficha
|
||||
|
||||
function linhaDeJogador(j) {
|
||||
var linha = el("div", "linha-ficha linha-jogador");
|
||||
if (j && j.id) linha.setAttribute("data-id", j.id);
|
||||
var nome = el("input");
|
||||
nome.placeholder = "Nome do jogador";
|
||||
nome.maxLength = 120;
|
||||
nome.value = (j && j.nome) || "";
|
||||
nome.setAttribute("aria-label", "Nome do jogador");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
var camisa = el("input");
|
||||
camisa.type = "number";
|
||||
camisa.min = "0";
|
||||
camisa.max = "999";
|
||||
camisa.placeholder = "nº";
|
||||
camisa.value = j && j.numeroCamisa !== undefined && j.numeroCamisa !== null ? j.numeroCamisa : "";
|
||||
camisa.setAttribute("aria-label", "Número da camisa");
|
||||
camisa.setAttribute("data-campo", "camisa");
|
||||
var tirar = el("button", "secundario", "×");
|
||||
tirar.type = "button";
|
||||
tirar.setAttribute("aria-label", "Remover jogador");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(camisa);
|
||||
linha.appendChild(tirar);
|
||||
listaJogadores.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
function linhaDeComissao(m) {
|
||||
var linha = el("div", "linha-ficha linha-comissao");
|
||||
if (m && m.id) linha.setAttribute("data-id", m.id);
|
||||
var nome = el("input");
|
||||
nome.placeholder = "Nome";
|
||||
nome.maxLength = 120;
|
||||
nome.value = (m && m.nome) || "";
|
||||
nome.setAttribute("aria-label", "Nome na comissão técnica");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
var papel = el("input");
|
||||
papel.placeholder = "Função";
|
||||
papel.maxLength = 40;
|
||||
papel.value = (m && m.funcao) || "";
|
||||
papel.setAttribute("list", "funcoes-comissao");
|
||||
papel.setAttribute("aria-label", "Função na comissão técnica");
|
||||
papel.setAttribute("data-campo", "funcao");
|
||||
var tirar = el("button", "secundario", "×");
|
||||
tirar.type = "button";
|
||||
tirar.setAttribute("aria-label", "Remover da comissão");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(papel);
|
||||
linha.appendChild(tirar);
|
||||
listaComissao.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
/** Time da escola ou ficha fechada: só leitura (listas simples). */
|
||||
function fichaSomenteLeitura(ficha) {
|
||||
formFicha.hidden = true;
|
||||
var alvo = document.getElementById("ficha-leitura");
|
||||
if (!alvo) {
|
||||
alvo = el("div", "somente-leitura");
|
||||
alvo.id = "ficha-leitura";
|
||||
formFicha.parentNode.appendChild(alvo);
|
||||
}
|
||||
limpar(alvo);
|
||||
alvo.appendChild(el("h3", null, "Jogadores"));
|
||||
var ul = el("ul");
|
||||
(ficha.jogadores || []).forEach(function (j) {
|
||||
ul.appendChild(el("li", null, pessoa({ nome: j.nome, numeroCamisa: j.numeroCamisa })));
|
||||
});
|
||||
if (!ul.childNodes.length) ul.appendChild(el("li", null, "Nenhum jogador na ficha."));
|
||||
alvo.appendChild(ul);
|
||||
if ((ficha.comissao || []).length) {
|
||||
alvo.appendChild(el("h3", null, "Comissão técnica"));
|
||||
var uc = el("ul");
|
||||
ficha.comissao.forEach(function (m) { uc.appendChild(el("li", null, m.nome + " — " + m.funcao)); });
|
||||
alvo.appendChild(uc);
|
||||
}
|
||||
}
|
||||
|
||||
function montarFicha(ficha) {
|
||||
var aviso = document.getElementById("ficha-aviso");
|
||||
limpar(listaJogadores);
|
||||
limpar(listaComissao);
|
||||
if (!ficha.editavel) {
|
||||
aviso.textContent = "Esta ficha está fechada para alterações aqui. Se algo estiver errado, fale com a organização.";
|
||||
fichaSomenteLeitura(ficha);
|
||||
return;
|
||||
}
|
||||
aviso.textContent = "Confira os jogadores (nome e número da camisa) e a comissão técnica. Dá para corrigir até o primeiro jogo ser encerrado.";
|
||||
formFicha.hidden = false;
|
||||
(ficha.jogadores || []).forEach(linhaDeJogador);
|
||||
if (!(ficha.jogadores || []).length) for (var i = 0; i < 3; i++) linhaDeJogador();
|
||||
(ficha.comissao || []).forEach(linhaDeComissao);
|
||||
}
|
||||
|
||||
function mostrarErroFicha(texto) {
|
||||
fichaErro.textContent = texto || "";
|
||||
fichaErro.hidden = !texto;
|
||||
}
|
||||
|
||||
function mostrarOkFicha(texto) {
|
||||
fichaOk.textContent = texto || "";
|
||||
fichaOk.hidden = !texto;
|
||||
}
|
||||
|
||||
function corpoDaFicha() {
|
||||
var jogadores = [];
|
||||
var linhas = listaJogadores.querySelectorAll(".linha-jogador");
|
||||
for (var i = 0; i < linhas.length; i++) {
|
||||
var nome = linhas[i].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!nome) continue;
|
||||
var c = linhas[i].querySelector('[data-campo="camisa"]').value;
|
||||
var j = { nome: nome, numeroCamisa: c === "" ? null : parseInt(c, 10) };
|
||||
if (linhas[i].getAttribute("data-id")) j.id = linhas[i].getAttribute("data-id");
|
||||
jogadores.push(j);
|
||||
}
|
||||
var comissao = [];
|
||||
var faltaFuncao = false;
|
||||
var pessoas = listaComissao.querySelectorAll(".linha-comissao");
|
||||
for (var k = 0; k < pessoas.length; k++) {
|
||||
var n = pessoas[k].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!n) continue;
|
||||
var f = pessoas[k].querySelector('[data-campo="funcao"]').value.trim();
|
||||
if (!f) faltaFuncao = true;
|
||||
var m = { nome: n, funcao: f };
|
||||
if (pessoas[k].getAttribute("data-id")) m.id = pessoas[k].getAttribute("data-id");
|
||||
comissao.push(m);
|
||||
}
|
||||
return { jogadores: jogadores, comissao: comissao, faltaFuncao: faltaFuncao };
|
||||
}
|
||||
|
||||
function aoSalvar(ev) {
|
||||
ev.preventDefault();
|
||||
mostrarErroFicha("");
|
||||
mostrarOkFicha("");
|
||||
var c = corpoDaFicha();
|
||||
if (c.jogadores.length > MAX_JOGADORES) { mostrarErroFicha("No máximo " + MAX_JOGADORES + " jogadores."); return; }
|
||||
if (c.faltaFuncao) { mostrarErroFicha("Informe a função de cada pessoa da comissão técnica."); return; }
|
||||
if (c.comissao.length > MAX_COMISSAO) { mostrarErroFicha("No máximo " + MAX_COMISSAO + " pessoas na comissão técnica."); return; }
|
||||
salvar.disabled = true;
|
||||
salvar.setAttribute("aria-busy", "true");
|
||||
fetch(base + "/ficha", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ jogadores: c.jogadores, comissao: c.comissao })
|
||||
}).then(lerResposta).then(function (r) {
|
||||
salvar.disabled = false;
|
||||
salvar.removeAttribute("aria-busy");
|
||||
if (r.status >= 200 && r.status < 300) {
|
||||
mostrarOkFicha("Ficha salva.");
|
||||
return carregar(true);
|
||||
}
|
||||
if (r.status === 404) { recusar("Este link não está mais disponível."); return null; }
|
||||
mostrarErroFicha((r.corpo && r.corpo.detail) || "Não foi possível salvar agora. Tente de novo em instantes.");
|
||||
return null;
|
||||
}).catch(function () {
|
||||
salvar.disabled = false;
|
||||
salvar.removeAttribute("aria-busy");
|
||||
mostrarErroFicha("Sem conexão. Confira a internet e tente de novo.");
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ carga
|
||||
|
||||
function mostrar(v, manterAba) {
|
||||
visao = v;
|
||||
var nomeTime = (v.time && v.time.nome) || "Seu time";
|
||||
titulo.textContent = (v.competicao || "Competição") + " — " + nomeTime;
|
||||
document.title = titulo.textContent + " — Athletic Map";
|
||||
var partes = [];
|
||||
if (v.time && v.time.escola) partes.push(v.time.escola);
|
||||
if (v.time && v.time.cidade) partes.push(v.time.cidade);
|
||||
if (v.temporada && v.temporada.nome) partes.push("Temporada " + v.temporada.nome);
|
||||
if (v.time && v.time.status === "DESQUALIFICADO") partes.push("Time desqualificado");
|
||||
situacao.textContent = partes.join(" · ");
|
||||
situacao.className = "";
|
||||
painel.hidden = false;
|
||||
montarTabela(v.classificacao, nomeTime);
|
||||
montarJogos(v.jogos);
|
||||
montarSumulas(v.sumulas, v.jogos);
|
||||
montarFicha(v.ficha || { jogadores: [], comissao: [], editavel: false });
|
||||
if (!manterAba) abrirAba("tabela", false);
|
||||
}
|
||||
|
||||
function carregar(manterAba) {
|
||||
return fetch(base).then(lerResposta).then(function (r) {
|
||||
if (r.status === 404) { recusar("Este link não está disponível ou foi desativado. Peça um novo à organização."); return; }
|
||||
if (r.status === 429) { recusar("Muitas consultas seguidas. Aguarde alguns minutos e abra o link de novo."); return; }
|
||||
if (r.status !== 200) { recusar("Não foi possível abrir agora. Tente de novo em instantes."); return; }
|
||||
mostrar(r.corpo || {}, manterAba);
|
||||
}).catch(function () {
|
||||
recusar("Não foi possível abrir agora. Tente de novo em instantes.");
|
||||
});
|
||||
}
|
||||
|
||||
function iniciar() {
|
||||
if (!TOKEN.test(token)) { recusar("Este endereço não é válido."); return; }
|
||||
ABAS.forEach(function (nome) {
|
||||
var aba = document.getElementById("aba-" + nome);
|
||||
aba.addEventListener("click", function () { abrirAba(nome, false); });
|
||||
aba.addEventListener("keydown", aoTeclarNaAba);
|
||||
});
|
||||
document.getElementById("maisJogador").addEventListener("click", function () {
|
||||
if (listaJogadores.querySelectorAll(".linha-jogador").length < MAX_JOGADORES) linhaDeJogador().querySelector("input").focus();
|
||||
});
|
||||
document.getElementById("maisComissao").addEventListener("click", function () {
|
||||
if (listaComissao.querySelectorAll(".linha-comissao").length < MAX_COMISSAO) linhaDeComissao(null).querySelector("input").focus();
|
||||
});
|
||||
formFicha.addEventListener("submit", aoSalvar);
|
||||
carregar(false);
|
||||
}
|
||||
|
||||
iniciar();
|
||||
})();
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
|
||||
@@ -162,6 +162,9 @@ spec:
|
||||
- { key: "preparando.js", path: "preparando.js" }
|
||||
- { key: "privacidade.html", path: "privacidade.html" }
|
||||
- { key: "termos.html", path: "termos.html" }
|
||||
- { key: "time.css", path: "time.css" }
|
||||
- { key: "time.html", path: "time.html" }
|
||||
- { key: "time.js", path: "time.js" }
|
||||
- configMap:
|
||||
name: frontend-publico-binario
|
||||
items:
|
||||
|
||||
@@ -409,7 +409,7 @@ data:
|
||||
|
||||
<form id="formulario" hidden novalidate>
|
||||
<p class="explica">
|
||||
Preencha os dados do seu time e dos jogadores (só o nome e o número da camisa).
|
||||
Preencha os dados do seu time, dos jogadores (só o nome e o número da camisa) e da comissão técnica (nome e função).
|
||||
A organização confere e aprova antes do time entrar na competição.
|
||||
</p>
|
||||
|
||||
@@ -433,12 +433,23 @@ data:
|
||||
<button type="button" id="maisJogador" class="secundario">+ Jogador</button>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Comissão técnica</legend>
|
||||
<div id="comissao"></div>
|
||||
<datalist id="funcoes-comissao">
|
||||
<option value="Técnico"></option><option value="Auxiliar técnico"></option><option value="Preparador físico"></option>
|
||||
<option value="Preparador de goleiros"></option><option value="Médico"></option><option value="Fisioterapeuta"></option>
|
||||
<option value="Massagista"></option><option value="Roupeiro"></option><option value="Dirigente"></option>
|
||||
</datalist>
|
||||
<button type="button" id="maisComissao" class="secundario">+ Pessoa na comissão</button>
|
||||
</fieldset>
|
||||
|
||||
<p id="erro" class="erro" role="alert" hidden></p>
|
||||
<button type="submit" id="enviar">Enviar time</button>
|
||||
</form>
|
||||
</main>
|
||||
<script src="/publico/desafio.js?v=1"></script>
|
||||
<script src="/publico/convite.js?v=2"></script>
|
||||
<script src="/publico/convite.js?v=3"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -465,6 +476,7 @@ data:
|
||||
|
||||
var TOKEN = /^[A-Za-z0-9_-]{20,64}$/;
|
||||
var MAX_JOGADORES = 60;
|
||||
var MAX_COMISSAO = 15;
|
||||
|
||||
var token = decodeURIComponent((location.pathname.split("/").filter(Boolean).pop() || ""));
|
||||
var base = "/api/public/competicoes/convites/" + encodeURIComponent(token);
|
||||
@@ -473,6 +485,7 @@ data:
|
||||
var situacao = document.getElementById("situacao");
|
||||
var formulario = document.getElementById("formulario");
|
||||
var listaJogadores = document.getElementById("jogadores");
|
||||
var listaComissao = document.getElementById("comissao");
|
||||
var erro = document.getElementById("erro");
|
||||
var enviar = document.getElementById("enviar");
|
||||
var ROTULO_ENVIAR = enviar.textContent;
|
||||
@@ -520,6 +533,39 @@ data:
|
||||
return linha;
|
||||
}
|
||||
|
||||
/** Comissao tecnica (21/10/2026): nome e funcao; a funcao tem sugestoes, mas aceita texto livre. */
|
||||
function linhaDeComissao(funcao) {
|
||||
var linha = document.createElement("div");
|
||||
linha.className = "linha-jogador linha-comissao";
|
||||
|
||||
var nome = document.createElement("input");
|
||||
nome.placeholder = "Nome";
|
||||
nome.maxLength = 120;
|
||||
nome.setAttribute("aria-label", "Nome na comissão técnica");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
|
||||
var papel = document.createElement("input");
|
||||
papel.placeholder = "Função";
|
||||
papel.maxLength = 40;
|
||||
papel.value = funcao || "";
|
||||
papel.setAttribute("list", "funcoes-comissao");
|
||||
papel.setAttribute("aria-label", "Função na comissão técnica");
|
||||
papel.setAttribute("data-campo", "funcao");
|
||||
|
||||
var tirar = document.createElement("button");
|
||||
tirar.type = "button";
|
||||
tirar.className = "secundario";
|
||||
tirar.textContent = "×";
|
||||
tirar.setAttribute("aria-label", "Remover da comissão");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(papel);
|
||||
linha.appendChild(tirar);
|
||||
listaComissao.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
function valor(id) {
|
||||
var v = document.getElementById(id).value.trim();
|
||||
return v === "" ? null : v;
|
||||
@@ -534,14 +580,26 @@ data:
|
||||
var c = linhas[i].querySelector('[data-campo="camisa"]').value;
|
||||
jogadores.push({ nome: nome, numeroCamisa: c === "" ? null : parseInt(c, 10) });
|
||||
}
|
||||
var comissao = [];
|
||||
var faltaFuncao = false;
|
||||
var pessoas = listaComissao.querySelectorAll(".linha-comissao");
|
||||
for (var k = 0; k < pessoas.length; k++) {
|
||||
var n = pessoas[k].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!n) continue;
|
||||
var f = pessoas[k].querySelector('[data-campo="funcao"]').value.trim();
|
||||
if (!f) faltaFuncao = true;
|
||||
comissao.push({ nome: n, funcao: f });
|
||||
}
|
||||
return {
|
||||
faltaFuncao: faltaFuncao,
|
||||
nome: valor("nome"),
|
||||
escola: valor("escola"),
|
||||
cidade: valor("cidade"),
|
||||
contatoNome: valor("contatoNome"),
|
||||
contatoTelefone: valor("contatoTelefone"),
|
||||
contatoEmail: valor("contatoEmail"),
|
||||
jogadores: jogadores
|
||||
jogadores: jogadores,
|
||||
comissao: comissao
|
||||
};
|
||||
}
|
||||
|
||||
@@ -555,6 +613,9 @@ data:
|
||||
var corpo = montarCorpo();
|
||||
if (!corpo.nome) { mostrarErro("Informe o nome do time."); return; }
|
||||
if (corpo.jogadores.length > MAX_JOGADORES) { mostrarErro("No máximo " + MAX_JOGADORES + " jogadores."); return; }
|
||||
if (corpo.faltaFuncao) { mostrarErro("Informe a função de cada pessoa da comissão técnica."); return; }
|
||||
if (corpo.comissao.length > MAX_COMISSAO) { mostrarErro("No máximo " + MAX_COMISSAO + " pessoas na comissão técnica."); return; }
|
||||
delete corpo.faltaFuncao;
|
||||
enviar.disabled = true;
|
||||
enviar.textContent = "Verificando…";
|
||||
enviar.setAttribute("aria-busy", "true");
|
||||
@@ -615,6 +676,7 @@ data:
|
||||
situacao.className = "";
|
||||
formulario.hidden = false;
|
||||
for (var i = 0; i < 5; i++) linhaDeJogador();
|
||||
linhaDeComissao("Técnico");
|
||||
}).catch(function () {
|
||||
recusar("Não foi possível abrir o convite agora. Tente de novo em instantes.");
|
||||
});
|
||||
@@ -623,6 +685,9 @@ data:
|
||||
document.getElementById("maisJogador").addEventListener("click", function () {
|
||||
if (listaJogadores.querySelectorAll(".linha-jogador").length < MAX_JOGADORES) linhaDeJogador().querySelector("input").focus();
|
||||
});
|
||||
document.getElementById("maisComissao").addEventListener("click", function () {
|
||||
if (listaComissao.querySelectorAll(".linha-comissao").length < MAX_COMISSAO) linhaDeComissao("").querySelector("input").focus();
|
||||
});
|
||||
formulario.addEventListener("submit", aoEnviar);
|
||||
iniciar();
|
||||
})();
|
||||
@@ -8242,6 +8307,557 @@ data:
|
||||
</body>
|
||||
</html>
|
||||
|
||||
time.css: |
|
||||
/* Link do time: página pública, sem fonte nem script de fora (a CSP só deixa 'self'). Mobile primeiro. */
|
||||
main { max-width: 56rem; }
|
||||
.explica, .legenda { color: var(--muted); font-size: .9rem; }
|
||||
.abas { display: flex; gap: .25rem; overflow-x: auto; border-bottom: 2px solid var(--line); margin: 0 0 1rem; }
|
||||
.abas button { background: transparent; color: var(--navy); border: 0; border-bottom: 3px solid transparent; border-radius: 0; padding: .6rem .9rem; white-space: nowrap; font: inherit; font-weight: 700; cursor: pointer; }
|
||||
.abas button[aria-selected="true"] { border-bottom-color: var(--orange); }
|
||||
.abas button:focus-visible, button:focus-visible, input:focus-visible { outline: 3px solid var(--orange); outline-offset: 1px; }
|
||||
h2 { font-size: 1.15rem; margin: .5rem 0; }
|
||||
.rolagem { overflow-x: auto; }
|
||||
table { border-collapse: collapse; width: 100%; font-size: .9rem; background: #fff; }
|
||||
th, td { padding: .4rem .45rem; text-align: center; border-bottom: 1px solid var(--line); white-space: nowrap; }
|
||||
th:nth-child(2), td:nth-child(2) { text-align: left; white-space: normal; min-width: 8rem; }
|
||||
tr.meu { background: #fff4e5; font-weight: 700; }
|
||||
.forma { display: inline-flex; gap: 3px; vertical-align: middle; }
|
||||
.ponto { width: .8rem; height: .8rem; border-radius: 50%; display: inline-block; }
|
||||
.ponto.V { background: #067647; }
|
||||
.ponto.E { background: #98a2b3; }
|
||||
.ponto.D { background: #b42318; }
|
||||
.cartao { display: inline-block; width: .7rem; height: .95rem; border-radius: 2px; vertical-align: middle; margin-right: 3px; }
|
||||
.cartao.amarelo { background: #f5c518; }
|
||||
.cartao.vermelho { background: #d92d20; }
|
||||
.jogo, .sumula { background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: .6rem .8rem; margin: 0 0 .6rem; }
|
||||
.jogo .quando { color: var(--muted); font-size: .85rem; }
|
||||
.jogo .confronto { display: grid; grid-template-columns: 1fr auto 1fr; gap: .5rem; align-items: center; font-weight: 700; }
|
||||
.jogo .confronto span:first-child { text-align: right; }
|
||||
.jogo .placar { background: var(--navy); color: #fff; border-radius: 6px; padding: .1rem .6rem; }
|
||||
.etiqueta { display: inline-block; font-size: .75rem; font-weight: 700; padding: .05rem .5rem; border-radius: 99px; background: #fff1e3; border: 1px solid var(--orange); margin-left: .4rem; }
|
||||
.sumula h3 { margin: 0 0 .3rem; font-size: 1rem; }
|
||||
.sumula ul { margin: .2rem 0 .5rem; padding-left: 1.2rem; }
|
||||
form { display: grid; gap: 1rem; }
|
||||
fieldset { border: 1px solid var(--line); border-radius: 8px; background: #fff; padding: .75rem 1rem 1rem; display: grid; gap: .6rem; }
|
||||
legend { font-weight: 700; padding: 0 .4rem; }
|
||||
input { font: inherit; padding: .5rem .6rem; border: 1px solid var(--line); border-radius: 6px; width: 100%; }
|
||||
.linha-ficha { display: grid; grid-template-columns: 1fr 5.5rem auto; gap: .4rem; align-items: center; }
|
||||
.linha-ficha button { padding: .4rem .6rem; }
|
||||
button { font: inherit; font-weight: 700; padding: .65rem 1.1rem; border: 0; border-radius: 8px; background: var(--orange); color: #fff; cursor: pointer; }
|
||||
button.secundario { background: #fff; color: var(--navy); border: 1px solid var(--line); justify-self: start; }
|
||||
button:disabled { opacity: .55; cursor: not-allowed; }
|
||||
.erro { color: #b42318; margin: 0; font-weight: 600; }
|
||||
.ok { background: #e7f6ec; border-left: 4px solid #067647; padding: .8rem 1rem; margin: 0; }
|
||||
#situacao.invalido { background: #fff1e3; border-left: 4px solid var(--orange); padding: 1rem 1.2rem; }
|
||||
.somente-leitura li { margin: .15rem 0; }
|
||||
@media (max-width: 30rem) { .linha-ficha { grid-template-columns: 1fr 4.5rem auto; } }
|
||||
|
||||
time.html: |
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<title>Seu time na competição — Athletic Map</title>
|
||||
<link rel="stylesheet" href="/publico/_estilo.css">
|
||||
<link rel="stylesheet" href="/publico/time.css?v=1">
|
||||
</head>
|
||||
<body>
|
||||
<header><div class="marca">Athletic <span>Map</span></div></header>
|
||||
<main>
|
||||
<h1 id="titulo">Seu time na competição</h1>
|
||||
<p id="situacao" role="status" aria-live="polite">Abrindo o link…</p>
|
||||
|
||||
<div id="painel" hidden>
|
||||
<div class="abas" role="tablist" aria-label="Seções">
|
||||
<button type="button" role="tab" id="aba-tabela" aria-controls="sec-tabela" aria-selected="true" data-aba="tabela">Tabela</button>
|
||||
<button type="button" role="tab" id="aba-jogos" aria-controls="sec-jogos" aria-selected="false" tabindex="-1" data-aba="jogos">Jogos</button>
|
||||
<button type="button" role="tab" id="aba-sumulas" aria-controls="sec-sumulas" aria-selected="false" tabindex="-1" data-aba="sumulas">Súmulas</button>
|
||||
<button type="button" role="tab" id="aba-ficha" aria-controls="sec-ficha" aria-selected="false" tabindex="-1" data-aba="ficha">Ficha do time</button>
|
||||
</div>
|
||||
|
||||
<section id="sec-tabela" role="tabpanel" aria-labelledby="aba-tabela">
|
||||
<h2>Tabela</h2>
|
||||
<div class="rolagem" id="tabela"></div>
|
||||
<p class="legenda">P pontos · J jogos · V vitórias · E empates · D derrotas · GP gols pró · GC gols contra · SG saldo de gols · % aproveitamento · CA cartões amarelos · CV cartões vermelhos</p>
|
||||
</section>
|
||||
|
||||
<section id="sec-jogos" role="tabpanel" aria-labelledby="aba-jogos" hidden>
|
||||
<h2>Jogos</h2>
|
||||
<div id="jogos"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-sumulas" role="tabpanel" aria-labelledby="aba-sumulas" hidden>
|
||||
<h2>Súmulas</h2>
|
||||
<div id="sumulas"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-ficha" role="tabpanel" aria-labelledby="aba-ficha" hidden>
|
||||
<h2>Ficha do time</h2>
|
||||
<p id="ficha-aviso" class="explica"></p>
|
||||
<form id="ficha" novalidate>
|
||||
<fieldset>
|
||||
<legend>Jogadores</legend>
|
||||
<div id="jogadores"></div>
|
||||
<button type="button" id="maisJogador" class="secundario">+ Jogador</button>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Comissão técnica</legend>
|
||||
<div id="comissao"></div>
|
||||
<datalist id="funcoes-comissao">
|
||||
<option value="Técnico"></option><option value="Auxiliar técnico"></option><option value="Preparador físico"></option>
|
||||
<option value="Preparador de goleiros"></option><option value="Médico"></option><option value="Fisioterapeuta"></option>
|
||||
<option value="Massagista"></option><option value="Roupeiro"></option><option value="Dirigente"></option>
|
||||
</datalist>
|
||||
<button type="button" id="maisComissao" class="secundario">+ Pessoa na comissão</button>
|
||||
</fieldset>
|
||||
<p id="ficha-erro" class="erro" role="alert" hidden></p>
|
||||
<p id="ficha-ok" class="ok" role="status" hidden></p>
|
||||
<button type="submit" id="salvar">Salvar ficha</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<script src="/publico/time.js?v=1"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
time.js: |
|
||||
/* ===========================================================================
|
||||
* Link do time — a página que UMA pessoa do time abre pelo link com chave.
|
||||
*
|
||||
* Sem login: a chave no endereço é o único segredo. A página mostra a tabela,
|
||||
* os jogos e as súmulas da competição e a ficha do time; se o time for de
|
||||
* fora e ainda não houver jogo encerrado, a ficha pode ser corrigida aqui.
|
||||
*
|
||||
* Tudo o que vem do servidor entra na página com `textContent` — nunca como
|
||||
* HTML. O script é externo porque a CSP é `script-src 'self'`.
|
||||
* =========================================================================== */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var TOKEN = /^[A-Za-z0-9_-]{20,64}$/;
|
||||
var MAX_JOGADORES = 60;
|
||||
var MAX_COMISSAO = 15;
|
||||
var ABAS = ["tabela", "jogos", "sumulas", "ficha"];
|
||||
var ROTULOS_FORMA = { V: "Vitória", E: "Empate", D: "Derrota" };
|
||||
var ROTULOS_STATUS = {
|
||||
AGENDADA: "Agendado", ADIADA: "Adiado", EM_ANDAMENTO: "Em andamento", ENCERRADA: "Encerrado", CANCELADA: "Cancelado"
|
||||
};
|
||||
|
||||
var token = decodeURIComponent(location.pathname.split("/").filter(Boolean).pop() || "");
|
||||
var base = "/api/public/competicoes/times/" + encodeURIComponent(token);
|
||||
|
||||
var titulo = document.getElementById("titulo");
|
||||
var situacao = document.getElementById("situacao");
|
||||
var painel = document.getElementById("painel");
|
||||
var listaJogadores = document.getElementById("jogadores");
|
||||
var listaComissao = document.getElementById("comissao");
|
||||
var formFicha = document.getElementById("ficha");
|
||||
var fichaErro = document.getElementById("ficha-erro");
|
||||
var fichaOk = document.getElementById("ficha-ok");
|
||||
var salvar = document.getElementById("salvar");
|
||||
var visao = null;
|
||||
|
||||
function el(tag, classe, texto) {
|
||||
var e = document.createElement(tag);
|
||||
if (classe) e.className = classe;
|
||||
if (texto !== undefined && texto !== null) e.textContent = String(texto);
|
||||
return e;
|
||||
}
|
||||
|
||||
function limpar(no) {
|
||||
while (no.firstChild) no.removeChild(no.firstChild);
|
||||
}
|
||||
|
||||
function recusar(texto) {
|
||||
situacao.textContent = texto;
|
||||
situacao.className = "invalido";
|
||||
painel.hidden = true;
|
||||
}
|
||||
|
||||
function lerResposta(r) {
|
||||
return r.json().catch(function () { return {}; }).then(function (corpo) { return { status: r.status, corpo: corpo }; });
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ abas
|
||||
|
||||
function abrirAba(nome, focar) {
|
||||
for (var i = 0; i < ABAS.length; i++) {
|
||||
var ativa = ABAS[i] === nome;
|
||||
var aba = document.getElementById("aba-" + ABAS[i]);
|
||||
aba.setAttribute("aria-selected", ativa ? "true" : "false");
|
||||
aba.tabIndex = ativa ? 0 : -1;
|
||||
document.getElementById("sec-" + ABAS[i]).hidden = !ativa;
|
||||
if (ativa && focar) aba.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function aoTeclarNaAba(ev) {
|
||||
var i = ABAS.indexOf(ev.currentTarget.getAttribute("data-aba"));
|
||||
if (ev.key === "ArrowRight") { abrirAba(ABAS[(i + 1) % ABAS.length], true); ev.preventDefault(); }
|
||||
else if (ev.key === "ArrowLeft") { abrirAba(ABAS[(i + ABAS.length - 1) % ABAS.length], true); ev.preventDefault(); }
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ tabela
|
||||
|
||||
function dataBr(iso) {
|
||||
if (!iso) return "Data a definir";
|
||||
var d = new Date(iso);
|
||||
if (isNaN(d.getTime())) return "Data a definir";
|
||||
try {
|
||||
return d.toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" });
|
||||
} catch (e) {
|
||||
return d.toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
function percentual(n) {
|
||||
return (Math.round((n || 0) * 10) / 10).toString().replace(".", ",") + "%";
|
||||
}
|
||||
|
||||
function montarTabela(linhas, meuNome) {
|
||||
var alvo = document.getElementById("tabela");
|
||||
limpar(alvo);
|
||||
if (!linhas || !linhas.length) { alvo.appendChild(el("p", "explica", "A tabela aparece quando houver times e jogos.")); return; }
|
||||
var t = el("table");
|
||||
t.appendChild(el("caption", "explica", "Classificação"));
|
||||
var cab = el("tr");
|
||||
var cols = [["#", "Posição"], ["Time", "Time"], ["P", "Pontos"], ["J", "Jogos"], ["V", "Vitórias"], ["E", "Empates"],
|
||||
["D", "Derrotas"], ["GP", "Gols pró"], ["GC", "Gols contra"], ["SG", "Saldo de gols"], ["%", "Aproveitamento"],
|
||||
["Últimos 5", "Últimos 5 jogos"], ["CA", "Cartões amarelos"], ["CV", "Cartões vermelhos"]];
|
||||
cols.forEach(function (c) {
|
||||
var th = el("th", null, c[0]);
|
||||
th.scope = "col";
|
||||
th.title = c[1];
|
||||
cab.appendChild(th);
|
||||
});
|
||||
var thead = el("thead");
|
||||
thead.appendChild(cab);
|
||||
t.appendChild(thead);
|
||||
var corpo = el("tbody");
|
||||
linhas.forEach(function (l) {
|
||||
var tr = el("tr", l.time === meuNome ? "meu" : "");
|
||||
[l.posicao, l.time, l.pontos, l.jogos, l.vitorias, l.empates, l.derrotas, l.golsPro, l.golsContra, l.saldoGols,
|
||||
percentual(l.aproveitamento)].forEach(function (v) { tr.appendChild(el("td", null, v)); });
|
||||
var forma = el("td");
|
||||
var caixa = el("span", "forma");
|
||||
(l.ultimos || []).forEach(function (r) {
|
||||
var p = el("span", "ponto " + r);
|
||||
p.title = ROTULOS_FORMA[r] || r;
|
||||
p.setAttribute("role", "img");
|
||||
p.setAttribute("aria-label", ROTULOS_FORMA[r] || r);
|
||||
caixa.appendChild(p);
|
||||
});
|
||||
forma.appendChild(caixa);
|
||||
tr.appendChild(forma);
|
||||
var am = el("td");
|
||||
am.appendChild(el("span", "cartao amarelo"));
|
||||
am.appendChild(document.createTextNode(String(l.amarelos || 0)));
|
||||
tr.appendChild(am);
|
||||
var vm = el("td");
|
||||
vm.appendChild(el("span", "cartao vermelho"));
|
||||
vm.appendChild(document.createTextNode(String(l.vermelhos || 0)));
|
||||
tr.appendChild(vm);
|
||||
corpo.appendChild(tr);
|
||||
});
|
||||
t.appendChild(corpo);
|
||||
alvo.appendChild(t);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ jogos
|
||||
|
||||
function montarJogos(jogos) {
|
||||
var alvo = document.getElementById("jogos");
|
||||
limpar(alvo);
|
||||
if (!jogos || !jogos.length) { alvo.appendChild(el("p", "explica", "Ainda não há jogos marcados.")); return; }
|
||||
jogos.forEach(function (j) {
|
||||
var card = el("div", "jogo");
|
||||
var quando = el("div", "quando", (j.rodada ? j.rodada + " · " : "") + dataBr(j.data));
|
||||
var etiqueta = el("span", "etiqueta", ROTULOS_STATUS[j.status] || j.status);
|
||||
quando.appendChild(etiqueta);
|
||||
if (j.wo) quando.appendChild(el("span", "etiqueta", "W.O."));
|
||||
card.appendChild(quando);
|
||||
var linha = el("div", "confronto");
|
||||
linha.appendChild(el("span", null, j.mandante));
|
||||
var temPlacar = j.placarMandante !== undefined && j.placarMandante !== null;
|
||||
linha.appendChild(el("span", temPlacar ? "placar" : "", temPlacar ? j.placarMandante + " x " + j.placarVisitante : "x"));
|
||||
linha.appendChild(el("span", null, j.visitante));
|
||||
card.appendChild(linha);
|
||||
if (j.penaltis) card.appendChild(el("div", "quando", "Pênaltis: " + j.penaltis.mandante + " x " + j.penaltis.visitante));
|
||||
if (j.motivo) card.appendChild(el("div", "quando", "Motivo: " + j.motivo));
|
||||
alvo.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ súmulas
|
||||
|
||||
function pessoa(p) {
|
||||
if (!p) return "jogador não identificado";
|
||||
if (p.nome && p.numeroCamisa !== undefined && p.numeroCamisa !== null) return p.nome + " (" + p.numeroCamisa + ")";
|
||||
if (p.nome) return p.nome;
|
||||
if (p.numeroCamisa !== undefined && p.numeroCamisa !== null) return "camisa " + p.numeroCamisa;
|
||||
return "jogador não identificado";
|
||||
}
|
||||
|
||||
function momento(e) {
|
||||
var m = e.minuto !== undefined && e.minuto !== null ? e.minuto + "'" : "";
|
||||
var t = e.tempo ? e.tempo + "º tempo" : "";
|
||||
return [t, m].filter(Boolean).join(" ") || "—";
|
||||
}
|
||||
|
||||
function secaoDaSumula(card, nome, itens, texto) {
|
||||
if (!itens || !itens.length) return;
|
||||
card.appendChild(el("strong", null, nome));
|
||||
var ul = el("ul");
|
||||
itens.forEach(function (i) { ul.appendChild(el("li", null, texto(i))); });
|
||||
card.appendChild(ul);
|
||||
}
|
||||
|
||||
function montarSumulas(sumulas, jogos) {
|
||||
var alvo = document.getElementById("sumulas");
|
||||
limpar(alvo);
|
||||
if (!sumulas || !sumulas.length) { alvo.appendChild(el("p", "explica", "As súmulas dos jogos do seu time aparecem aqui.")); return; }
|
||||
sumulas.forEach(function (s) {
|
||||
var j = (jogos || []).filter(function (x) { return x.ref === s.jogoRef; })[0];
|
||||
var card = el("div", "sumula");
|
||||
var cab = j ? j.mandante + " x " + j.visitante : "Jogo " + s.jogoRef;
|
||||
if (j && j.placarMandante !== undefined && j.placarMandante !== null) {
|
||||
cab = j.mandante + " " + j.placarMandante + " x " + j.placarVisitante + " " + j.visitante;
|
||||
}
|
||||
card.appendChild(el("h3", null, cab));
|
||||
if (j) card.appendChild(el("div", "quando", (j.rodada ? j.rodada + " · " : "") + dataBr(j.data)));
|
||||
secaoDaSumula(card, "Gols", s.gols, function (g) { return momento(g) + " · " + g.time + " · " + pessoa(g.jogador); });
|
||||
secaoDaSumula(card, "Cartões", s.cartoes, function (c) {
|
||||
return momento(c) + " · " + (c.tipo === "VERMELHO" ? "vermelho" : "amarelo") + " · " + c.time + " · " + pessoa(c.jogador);
|
||||
});
|
||||
secaoDaSumula(card, "Substituições", s.substituicoes, function (t) {
|
||||
return momento(t) + " · " + t.time + " · sai " + pessoa(t.sai) + ", entra " + pessoa(t.entra);
|
||||
});
|
||||
alvo.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ ficha
|
||||
|
||||
function linhaDeJogador(j) {
|
||||
var linha = el("div", "linha-ficha linha-jogador");
|
||||
if (j && j.id) linha.setAttribute("data-id", j.id);
|
||||
var nome = el("input");
|
||||
nome.placeholder = "Nome do jogador";
|
||||
nome.maxLength = 120;
|
||||
nome.value = (j && j.nome) || "";
|
||||
nome.setAttribute("aria-label", "Nome do jogador");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
var camisa = el("input");
|
||||
camisa.type = "number";
|
||||
camisa.min = "0";
|
||||
camisa.max = "999";
|
||||
camisa.placeholder = "nº";
|
||||
camisa.value = j && j.numeroCamisa !== undefined && j.numeroCamisa !== null ? j.numeroCamisa : "";
|
||||
camisa.setAttribute("aria-label", "Número da camisa");
|
||||
camisa.setAttribute("data-campo", "camisa");
|
||||
var tirar = el("button", "secundario", "×");
|
||||
tirar.type = "button";
|
||||
tirar.setAttribute("aria-label", "Remover jogador");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(camisa);
|
||||
linha.appendChild(tirar);
|
||||
listaJogadores.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
function linhaDeComissao(m) {
|
||||
var linha = el("div", "linha-ficha linha-comissao");
|
||||
if (m && m.id) linha.setAttribute("data-id", m.id);
|
||||
var nome = el("input");
|
||||
nome.placeholder = "Nome";
|
||||
nome.maxLength = 120;
|
||||
nome.value = (m && m.nome) || "";
|
||||
nome.setAttribute("aria-label", "Nome na comissão técnica");
|
||||
nome.setAttribute("data-campo", "nome");
|
||||
var papel = el("input");
|
||||
papel.placeholder = "Função";
|
||||
papel.maxLength = 40;
|
||||
papel.value = (m && m.funcao) || "";
|
||||
papel.setAttribute("list", "funcoes-comissao");
|
||||
papel.setAttribute("aria-label", "Função na comissão técnica");
|
||||
papel.setAttribute("data-campo", "funcao");
|
||||
var tirar = el("button", "secundario", "×");
|
||||
tirar.type = "button";
|
||||
tirar.setAttribute("aria-label", "Remover da comissão");
|
||||
tirar.addEventListener("click", function () { linha.remove(); });
|
||||
linha.appendChild(nome);
|
||||
linha.appendChild(papel);
|
||||
linha.appendChild(tirar);
|
||||
listaComissao.appendChild(linha);
|
||||
return linha;
|
||||
}
|
||||
|
||||
/** Time da escola ou ficha fechada: só leitura (listas simples). */
|
||||
function fichaSomenteLeitura(ficha) {
|
||||
formFicha.hidden = true;
|
||||
var alvo = document.getElementById("ficha-leitura");
|
||||
if (!alvo) {
|
||||
alvo = el("div", "somente-leitura");
|
||||
alvo.id = "ficha-leitura";
|
||||
formFicha.parentNode.appendChild(alvo);
|
||||
}
|
||||
limpar(alvo);
|
||||
alvo.appendChild(el("h3", null, "Jogadores"));
|
||||
var ul = el("ul");
|
||||
(ficha.jogadores || []).forEach(function (j) {
|
||||
ul.appendChild(el("li", null, pessoa({ nome: j.nome, numeroCamisa: j.numeroCamisa })));
|
||||
});
|
||||
if (!ul.childNodes.length) ul.appendChild(el("li", null, "Nenhum jogador na ficha."));
|
||||
alvo.appendChild(ul);
|
||||
if ((ficha.comissao || []).length) {
|
||||
alvo.appendChild(el("h3", null, "Comissão técnica"));
|
||||
var uc = el("ul");
|
||||
ficha.comissao.forEach(function (m) { uc.appendChild(el("li", null, m.nome + " — " + m.funcao)); });
|
||||
alvo.appendChild(uc);
|
||||
}
|
||||
}
|
||||
|
||||
function montarFicha(ficha) {
|
||||
var aviso = document.getElementById("ficha-aviso");
|
||||
limpar(listaJogadores);
|
||||
limpar(listaComissao);
|
||||
if (!ficha.editavel) {
|
||||
aviso.textContent = "Esta ficha está fechada para alterações aqui. Se algo estiver errado, fale com a organização.";
|
||||
fichaSomenteLeitura(ficha);
|
||||
return;
|
||||
}
|
||||
aviso.textContent = "Confira os jogadores (nome e número da camisa) e a comissão técnica. Dá para corrigir até o primeiro jogo ser encerrado.";
|
||||
formFicha.hidden = false;
|
||||
(ficha.jogadores || []).forEach(linhaDeJogador);
|
||||
if (!(ficha.jogadores || []).length) for (var i = 0; i < 3; i++) linhaDeJogador();
|
||||
(ficha.comissao || []).forEach(linhaDeComissao);
|
||||
}
|
||||
|
||||
function mostrarErroFicha(texto) {
|
||||
fichaErro.textContent = texto || "";
|
||||
fichaErro.hidden = !texto;
|
||||
}
|
||||
|
||||
function mostrarOkFicha(texto) {
|
||||
fichaOk.textContent = texto || "";
|
||||
fichaOk.hidden = !texto;
|
||||
}
|
||||
|
||||
function corpoDaFicha() {
|
||||
var jogadores = [];
|
||||
var linhas = listaJogadores.querySelectorAll(".linha-jogador");
|
||||
for (var i = 0; i < linhas.length; i++) {
|
||||
var nome = linhas[i].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!nome) continue;
|
||||
var c = linhas[i].querySelector('[data-campo="camisa"]').value;
|
||||
var j = { nome: nome, numeroCamisa: c === "" ? null : parseInt(c, 10) };
|
||||
if (linhas[i].getAttribute("data-id")) j.id = linhas[i].getAttribute("data-id");
|
||||
jogadores.push(j);
|
||||
}
|
||||
var comissao = [];
|
||||
var faltaFuncao = false;
|
||||
var pessoas = listaComissao.querySelectorAll(".linha-comissao");
|
||||
for (var k = 0; k < pessoas.length; k++) {
|
||||
var n = pessoas[k].querySelector('[data-campo="nome"]').value.trim();
|
||||
if (!n) continue;
|
||||
var f = pessoas[k].querySelector('[data-campo="funcao"]').value.trim();
|
||||
if (!f) faltaFuncao = true;
|
||||
var m = { nome: n, funcao: f };
|
||||
if (pessoas[k].getAttribute("data-id")) m.id = pessoas[k].getAttribute("data-id");
|
||||
comissao.push(m);
|
||||
}
|
||||
return { jogadores: jogadores, comissao: comissao, faltaFuncao: faltaFuncao };
|
||||
}
|
||||
|
||||
function aoSalvar(ev) {
|
||||
ev.preventDefault();
|
||||
mostrarErroFicha("");
|
||||
mostrarOkFicha("");
|
||||
var c = corpoDaFicha();
|
||||
if (c.jogadores.length > MAX_JOGADORES) { mostrarErroFicha("No máximo " + MAX_JOGADORES + " jogadores."); return; }
|
||||
if (c.faltaFuncao) { mostrarErroFicha("Informe a função de cada pessoa da comissão técnica."); return; }
|
||||
if (c.comissao.length > MAX_COMISSAO) { mostrarErroFicha("No máximo " + MAX_COMISSAO + " pessoas na comissão técnica."); return; }
|
||||
salvar.disabled = true;
|
||||
salvar.setAttribute("aria-busy", "true");
|
||||
fetch(base + "/ficha", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ jogadores: c.jogadores, comissao: c.comissao })
|
||||
}).then(lerResposta).then(function (r) {
|
||||
salvar.disabled = false;
|
||||
salvar.removeAttribute("aria-busy");
|
||||
if (r.status >= 200 && r.status < 300) {
|
||||
mostrarOkFicha("Ficha salva.");
|
||||
return carregar(true);
|
||||
}
|
||||
if (r.status === 404) { recusar("Este link não está mais disponível."); return null; }
|
||||
mostrarErroFicha((r.corpo && r.corpo.detail) || "Não foi possível salvar agora. Tente de novo em instantes.");
|
||||
return null;
|
||||
}).catch(function () {
|
||||
salvar.disabled = false;
|
||||
salvar.removeAttribute("aria-busy");
|
||||
mostrarErroFicha("Sem conexão. Confira a internet e tente de novo.");
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ carga
|
||||
|
||||
function mostrar(v, manterAba) {
|
||||
visao = v;
|
||||
var nomeTime = (v.time && v.time.nome) || "Seu time";
|
||||
titulo.textContent = (v.competicao || "Competição") + " — " + nomeTime;
|
||||
document.title = titulo.textContent + " — Athletic Map";
|
||||
var partes = [];
|
||||
if (v.time && v.time.escola) partes.push(v.time.escola);
|
||||
if (v.time && v.time.cidade) partes.push(v.time.cidade);
|
||||
if (v.temporada && v.temporada.nome) partes.push("Temporada " + v.temporada.nome);
|
||||
if (v.time && v.time.status === "DESQUALIFICADO") partes.push("Time desqualificado");
|
||||
situacao.textContent = partes.join(" · ");
|
||||
situacao.className = "";
|
||||
painel.hidden = false;
|
||||
montarTabela(v.classificacao, nomeTime);
|
||||
montarJogos(v.jogos);
|
||||
montarSumulas(v.sumulas, v.jogos);
|
||||
montarFicha(v.ficha || { jogadores: [], comissao: [], editavel: false });
|
||||
if (!manterAba) abrirAba("tabela", false);
|
||||
}
|
||||
|
||||
function carregar(manterAba) {
|
||||
return fetch(base).then(lerResposta).then(function (r) {
|
||||
if (r.status === 404) { recusar("Este link não está disponível ou foi desativado. Peça um novo à organização."); return; }
|
||||
if (r.status === 429) { recusar("Muitas consultas seguidas. Aguarde alguns minutos e abra o link de novo."); return; }
|
||||
if (r.status !== 200) { recusar("Não foi possível abrir agora. Tente de novo em instantes."); return; }
|
||||
mostrar(r.corpo || {}, manterAba);
|
||||
}).catch(function () {
|
||||
recusar("Não foi possível abrir agora. Tente de novo em instantes.");
|
||||
});
|
||||
}
|
||||
|
||||
function iniciar() {
|
||||
if (!TOKEN.test(token)) { recusar("Este endereço não é válido."); return; }
|
||||
ABAS.forEach(function (nome) {
|
||||
var aba = document.getElementById("aba-" + nome);
|
||||
aba.addEventListener("click", function () { abrirAba(nome, false); });
|
||||
aba.addEventListener("keydown", aoTeclarNaAba);
|
||||
});
|
||||
document.getElementById("maisJogador").addEventListener("click", function () {
|
||||
if (listaJogadores.querySelectorAll(".linha-jogador").length < MAX_JOGADORES) linhaDeJogador().querySelector("input").focus();
|
||||
});
|
||||
document.getElementById("maisComissao").addEventListener("click", function () {
|
||||
if (listaComissao.querySelectorAll(".linha-comissao").length < MAX_COMISSAO) linhaDeComissao(null).querySelector("input").focus();
|
||||
});
|
||||
formFicha.addEventListener("submit", aoSalvar);
|
||||
carregar(false);
|
||||
}
|
||||
|
||||
iniciar();
|
||||
})();
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
|
||||
Reference in New Issue
Block a user