/**
* @file
*
* Summary.
* <p>Clone do sistema de votação brasileiro.</p>
*
* Description.
* <p>A votação consiste em escolher um candidato a vereador
* e outro para prefeito.
* </p>
*
* <p>O frontend é totalmente escrito em javascript/html/css e o backend
* em php e mysql.</p>
*
* <p>Essa versão pode ser hospedada no {@link https://vercel.com/dashboard Vercel},
* que é uma solução {@link https://jamstack.wtf/#history Jamstack},
* e armazenar o banco {@link https://dev.mysql.com/downloads/mysql/ MySQL}
* no {@link https://railway.app Railway}.<br>
* Por isso, os arquivos foram colocados no diretório
* {@link https://vercel.com/docs/concepts/deployments/configure-a-build#output-directory public}.
* </p>
*
* <p>Toda aplicação de médio-grande porte deve lidar com informações sensíveis.<br>
* Exemplos deste tipo de informação são as credenciais para acessar banco de dados e
* {@link https://www.fortinet.com/resources/cyberglossary/api-key chaves API} de terceiros.</p>
*
* Se esse dados não forem criptografados {@link https://brightlineit.com/encryption-at-rest-important-business/ em repouso},
* atacantes podem conseguir acessá-los e usá-los com propósitos maliciosos.
* É aí onde entra em cena, uma solução como o
* {@link https://www.doppler.com/blog/configuring-php-applications-using-environment-variables# Doppler}.
*
* Nota: não existe nenhum tipo de validação de eleitores ainda.
*
* <pre>
* <code>
* {@link https://nodejs.org/en Node.js} installation (optional):
* - Ubuntu via {@link https://www.digitalocean.com/community/tutorials/how-to-install-node-js-on-ubuntu-20-04#option-2-installing-node-js-with-apt-using-a-nodesource-ppa ppa}:
* - sudo apt install nodejs
* - MacOS via {@link https://www.macports.org MacPorts}:
* - sudo port install <a href="https://ports.macports.org/port/nodejs18/details/">nodejs18</a>
* - sudo port install <a href="https://ports.macports.org/port/npm9/">npm9</a>
* Documentation:
* - Ubuntu:
* - sudo apt install jsdoc-toolkit
* or for a <a href="https://github.com/jsdoc/jsdoc">newer version</a>
* - sudo npm install -g <a href="https://www.npmjs.com/package/jsdoc">jsdoc</a>
* - MacOS:
* - sudo port install npm8 (or npm9)
* - sudo npm install -g jsdoc
* - jsdoc -r -c ./conf.json -d jsdoc urna/js
*
* Para iniciar o banco no {@link https://docs.railway.app/databases/mysql Railway}, basta fazer:
* - mysql -hcontainers-us-west-146.railway.app -uroot -p********************
* --port 6990 --protocol=TCP railway < <a href="../migration.sql">migration.sql</a>
* </code>
* </pre>
*
* <img src="../public/img/screenshot.jpg">
*
* @see <a href="/cwdc/7-mysql/urna-vercel/public/index.html">link</a>
* @see <a href="/cwdc/7-mysql/urna-vercel/public/src/script.js">source</a>
* @see https://www.youtube.com/watch?v=hF_VMWnsY00
* @see https://github.com/ivanfilho21/bonieky-live-javascript
* @see https://www.tse.jus.br/videos/tse-simule-como-votar-na-urna-eletronica-no-site-do-tse
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLAudioElement/Audio
* @see https://deliverystack.net/2021/11/04/deploy-a-static-file-website-to-vercel/
* @see https://docs.railway.app/databases/mysql
* @author Bonieky Lacerda e modificado por Paulo Roma Cavalcanti
* @since 01/08/2022
*/
"use strict";
const rVotoPara = document.querySelector(".esquerda .rotulo.r1 span");
const rCargo = document.querySelector(".esquerda .rotulo.r2 span");
const numeros = document.querySelector(".esquerda .rotulo.r3");
const rDescricao = document.querySelector(".esquerda .rotulo.r4");
const rMensagem = document.querySelector(".esquerda .rotulo.r4 .mensagem");
const rNomeCandidato = document.querySelector(
".esquerda .rotulo.r4 .nome-candidato"
);
const rPartidoPolitico = document.querySelector(
".esquerda .rotulo.r4 .partido-politico"
);
const rNomeVice = document.querySelector(".esquerda .rotulo.r4 .nome-vice");
const rRodape = document.querySelector(".tela .rodape");
const rCandidato = document.querySelector(".direita .candidato");
const rVice = document.querySelector(".direita .candidato.menor");
const votos = [];
const audioPath = "audio";
const imgPath = "img";
const apiPath = "./";
/**
* 0: vereador, 1: prefeito
* @type {Number}
*/
var etapaAtual = 0;
/**
* Array com os candidatos a vereador, na posição 0, e prefeito, na posição 1.
* <ul>
* <li>0 {titulo: "vereador", numeros: 5, candidatos: Object}</li>
* <li>1 {titulo: "prefeito", numeros: 2, candidatos: Object}</li>
* </ul>
* candidatos:
* <pre>
* {
* 15123: {nome: "Filho", partido: "MDB", foto: "cv4.jpg"},
* 27222: {nome: "Joel Varão", partido: "PSDC", foto: "cv5.jpg"},
* 43333: {nome: "Dandor", partido: "PV", foto: "cv3.jpg"},
* 45000: {nome: "Professor Clebson Almeida", partido: "PSDB", foto: "cv6.jpg"},
* 51222: {nome: "Christianne Varão", partido: "PEN", foto: "cv1.jpg"},
* 55555: {nome: "Homero do Zé Filho", partido: "PSL", foto: "cv2.jpg"},
* nulov: {nome: "NULO", partido: "Nenhum", foto: "cv5.jpg"}
* }
* </pre>
*
* @type {Array<{titulo: String, numeros: Number, candidatos: {String: {nome: String, partido: String, foto: String}}}>}
* @see https://flexiple.com/javascript/associative-array-javascript/
* @see https://linuxhint.com/javascript-associative-array/
*/
var etapas = null;
/**
* Número de candidato digitado.
* @type {String}
*/
var numeroDigitado = "";
/**
* Se votou em branco ou não.
* @type {Boolean}
*/
var votoEmBranco = false;
/**
* Acessa o servidor, que retorna uma resposta em json, contendo
* os candidatos. O servidor monta o json a partir do banco de dados.
* O arquivo etapas.json não é mais necessário.
*/
ajax(`${apiPath}/candidatos.php`, "GET", (response) => {
etapas = JSON.parse(response);
console.log(etapas);
comecarEtapa();
});
window.onload = () => {
let btns = document.querySelectorAll(".teclado--botao");
for (let btn of btns) {
btn.onclick = () => {
clicar(btn.innerHTML);
};
}
document.querySelector(".teclado--botao.branco").onclick = () => branco();
document.querySelector(".teclado--botao.laranja").onclick = () => corrigir();
document.querySelector(".teclado--botao.verde").onclick = () => confirmar();
};
/**
* Inicia a etapa atual.
*/
function comecarEtapa() {
let etapa = etapas[etapaAtual];
console.log("Etapa atual: " + etapa["titulo"]);
numeroDigitado = "";
votoEmBranco = false;
numeros.style.display = "flex";
numeros.innerHTML = "";
rVotoPara.style.display = "none";
rCandidato.style.display = "none";
rVice.style.display = "none";
rDescricao.style.display = "none";
rMensagem.style.display = "none";
rNomeCandidato.style.display = "none";
rPartidoPolitico.style.display = "none";
rNomeVice.style.display = "none";
rRodape.style.display = "none";
for (let i = 0; i < etapa["numeros"]; i++) {
let pisca = i == 0 ? " pisca" : "";
numeros.innerHTML += `
<div class="numero${pisca}"></div>
`;
}
rCargo.innerHTML = etapa["titulo"];
}
/**
* Procura o candidato pelo número digitado,
* se encontrar, mostra os dados dele na tela.
*/
function atualizarInterface() {
console.log("Número Digitado:", numeroDigitado);
let etapa = etapas[etapaAtual];
let candidato = null;
for (let num in etapa["candidatos"]) {
if (num == numeroDigitado) {
candidato = etapa["candidatos"][num];
break;
}
}
console.log("Candidato: " + candidato);
rVotoPara.style.display = "inline";
rDescricao.style.display = "block";
rNomeCandidato.style.display = "block";
rPartidoPolitico.style.display = "block";
if (candidato) {
let vice = candidato["vice"];
rRodape.style.display = "block";
rNomeCandidato.querySelector("span").innerHTML = candidato["nome"];
rPartidoPolitico.querySelector("span").innerHTML = candidato["partido"];
rCandidato.style.display = "block";
rCandidato.querySelector(
".imagem img"
).src = `${imgPath}/${candidato["foto"]}`;
rCandidato.querySelector(".cargo p").innerHTML = etapa["titulo"];
if (vice) {
rNomeVice.style.display = "block";
rNomeVice.querySelector("span").innerHTML = vice["nome"];
rVice.style.display = "block";
rVice.querySelector(".imagem img").src = `${imgPath}/${vice["foto"]}`;
} else {
rNomeVice.style.display = "none";
}
return;
}
if (votoEmBranco) return;
// Anular o voto
rNomeCandidato.style.display = "none";
rPartidoPolitico.style.display = "none";
rNomeVice.style.display = "none";
rMensagem.style.display = "block";
rMensagem.classList.add("pisca");
rMensagem.innerHTML = "VOTO NULO";
}
/**
* Verifica se pode usar o teclado e atualiza o número.
*
* @param {String} value numerical value of the key pressed.
*/
function clicar(value) {
console.log(value);
let elNum = document.querySelector(".esquerda .rotulo.r3 .numero.pisca");
if (elNum && !votoEmBranco) {
numeroDigitado += value;
elNum.innerHTML = value;
elNum.classList.remove("pisca");
let proximoNumero = elNum.nextElementSibling;
if (proximoNumero) {
proximoNumero.classList.add("pisca");
} else {
atualizarInterface();
}
// audio element can't play again.
new Audio(`${audioPath}/se1.mp3`).play();
}
}
/**
* Verifica se há número digitado, se não,
* vota em branco.
*/
function branco() {
console.log("branco");
// Verifica se há algum número digitado,
// se sim, não vota
if (!numeroDigitado) {
votoEmBranco = true;
numeros.style.display = "none";
rVotoPara.style.display = "inline";
rDescricao.style.display = "block";
rMensagem.style.display = "block";
rMensagem.innerHTML = "VOTO EM BRANCO";
new Audio(`${audioPath}/se1.mp3`).play();
}
}
/**
* Reinicia a etapa atual.
*/
function corrigir() {
console.log("corrigir");
new Audio(`${audioPath}/se2.mp3`).play();
comecarEtapa();
}
/**
* Confirma o número selecionado.
*/
function confirmar() {
console.log("confirmar");
let etapa = etapas[etapaAtual];
if (numeroDigitado.length == etapa["numeros"]) {
if (etapa["candidatos"][numeroDigitado]) {
// Votou em candidato
votos.push({
etapa: etapa["titulo"],
numero: numeroDigitado,
});
console.log(`Votou em ${numeroDigitado}`);
} else {
// Votou nulo
votos.push({
etapa: etapa["titulo"],
numero: null,
});
console.log("Votou Nulo");
}
} else if (votoEmBranco) {
// Votou em branco
votos.push({
etapa: etapa["titulo"],
numero: "",
});
console.log("Votou em Branco");
} else {
// Voto não pode ser confirmado
console.log("Voto não pode ser confirmado");
return;
}
new Audio(`${audioPath}/se3.mp3`).play();
if (etapas[etapaAtual + 1]) {
etapaAtual++;
comecarEtapa();
} else {
// A única coisa que muda:
// Registra os votos do usuário no servidor
enviarVotos();
document.querySelector(".tela").innerHTML = `
<div class="fim">FIM</div>
`;
}
}
/**
* Envia os votos do usuário para o servidor php
*/
function enviarVotos() {
ajax(
`${apiPath}/registrarVoto.php`,
"POST",
(response) => {
console.log("Voto registrado no MySQL: " + response);
},
JSON.stringify({ votos: votos })
);
}