feat: Melhorias de segurança e interface - Segurança: Implementação de CSRF token em formulários, validação no backend, proteção AJAX - QR Code: Preservação do otp_secret, evita geração desnecessária - Interface: Correções visuais, padronização de cores, melhorias em formulários

This commit is contained in:
andersonid
2025-04-04 11:37:48 -03:00
parent 9ffc562357
commit 56b8e7aa54
10 changed files with 1358 additions and 300 deletions

View File

@@ -7,6 +7,13 @@
--green: #198754;
--cyan: #0dcaf0;
--yellow: #ffc107;
--primary-color: #dc3545;
--primary-hover: #bb2d3b;
--text-color: #333;
--text-muted: #6c757d;
--bg-hover: #f8f9fa;
--tab-active-color: var(--primary-color);
--tab-hover-color: rgba(220, 53, 69, 0.1);
}
/* Tabelas */
@@ -241,4 +248,171 @@
.welcome-header h4 {
font-size: 1.25rem;
font-weight: 400;
}
/* Tabs */
.nav-tabs {
border-bottom: 2px solid var(--border-color);
margin-bottom: 1rem;
}
.nav-tabs .nav-link,
.nav-tabs .nav-link:focus,
.nav-tabs .nav-link:hover,
.nav-tabs .nav-link.active {
color: var(--primary-color) !important;
border: none;
border-bottom: 2px solid transparent;
padding: 0.75rem 1.5rem;
margin-bottom: -2px;
transition: all 0.2s ease-in-out;
font-weight: 500;
background-color: transparent;
}
.nav-tabs .nav-link:hover {
background-color: var(--tab-hover-color);
border-bottom: 2px solid var(--primary-color);
}
.nav-tabs .nav-link.active {
font-weight: 600;
background-color: var(--tab-hover-color);
border-bottom: 2px solid var(--primary-color);
}
.nav-tabs .nav-link i {
margin-right: 0.5rem;
}
.tab-content {
padding: 1rem 0;
}
.tab-pane {
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Responsividade das abas */
@media (max-width: 768px) {
.nav-tabs {
flex-wrap: nowrap;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.nav-tabs .nav-link {
white-space: nowrap;
padding: 0.5rem 1rem;
}
}
/* Estilo para botões com largura fixa */
.btn-fixed-width {
min-width: 120px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.375rem 0.75rem;
text-align: center;
height: 38px;
line-height: 1.5;
vertical-align: middle;
}
.btn-fixed-width i {
margin-right: 8px;
font-size: 0.875rem;
}
/* Estilo para o backdrop com blur em todos os modais */
.modal-backdrop.show {
backdrop-filter: blur(8px);
background-color: rgba(0, 0, 0, 0.7);
}
/* Estilo para o botão de fechar dos modais */
.btn-close {
background-color: transparent;
padding: 0.5rem;
opacity: 0.8;
transition: opacity 0.2s;
filter: invert(1) grayscale(100%) brightness(200%);
}
.btn-close:hover {
opacity: 1;
background-color: transparent;
}
/* Estilos do Modal */
.modal-header {
background-color: #343a40;
color: #fff;
padding: 1rem;
}
.modal-title {
color: #fff;
font-weight: 600;
display: flex;
align-items: center;
}
.modal-header i {
color: #fff;
margin-right: 0.5rem;
}
.modal-header .btn-close {
filter: invert(1) grayscale(100%) brightness(200%);
opacity: 0.8;
}
.modal-header .btn-close:hover {
opacity: 1;
}
/* Estilos globais de formulário */
.form-control:focus,
.form-select:focus,
.form-check-input:focus,
.btn:focus,
.btn-check:focus + .btn {
border-color: var(--primary-color);
box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
}
.form-control:hover,
.form-select:hover {
border-color: var(--primary-color);
}
/* Input group com foco */
.input-group .form-control:focus,
.input-group .form-select:focus {
border-color: var(--primary-color);
}
/* Checkbox e radio */
.form-check-input:checked {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
/* Date picker */
input[type="date"]:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
}

View File

@@ -61,6 +61,8 @@ function filtrarMilitantes() {
// Configurar eventos quando o DOM estiver carregado
document.addEventListener('DOMContentLoaded', function() {
console.log('DOM carregado, configurando eventos...');
// Configurar pesquisa
const searchInput = document.getElementById('searchInput');
if (searchInput) {
@@ -109,7 +111,7 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
console.log('DOM carregado');
console.log('Configurando modal de edição...');
// Configuração do modal de edição
const modalEditarMilitante = document.getElementById('modalEditarMilitante');
@@ -118,6 +120,10 @@ document.addEventListener('DOMContentLoaded', function() {
if (modalEditarMilitante) {
console.log('Modal encontrado, configurando eventos...');
// Criar instância do modal Bootstrap
const modalInstance = new bootstrap.Modal(modalEditarMilitante);
console.log('Instância do modal criada:', modalInstance);
modalEditarMilitante.addEventListener('show.bs.modal', function(event) {
console.log('Modal sendo exibido');
const button = event.relatedTarget;
@@ -129,33 +135,155 @@ document.addEventListener('DOMContentLoaded', function() {
}
const militanteId = button.getAttribute('data-militante-id');
const militanteNome = button.getAttribute('data-militante-nome');
console.log('ID do militante:', militanteId);
console.log('Nome do militante:', militanteNome);
// Dados do militante
const dados = {
nome: button.getAttribute('data-militante-nome'),
cpf: button.getAttribute('data-militante-cpf'),
email: button.getAttribute('data-militante-email'),
telefone: button.getAttribute('data-militante-telefone'),
endereco: button.getAttribute('data-militante-endereco'),
filiado: button.getAttribute('data-militante-filiado')
};
console.log('Dados do militante:', dados);
if (!militanteId) {
console.error('ID do militante não encontrado no botão!');
return;
}
// Preencher campos
document.getElementById('editNome').value = dados.nome || '';
document.getElementById('editCpf').value = dados.cpf || '';
document.getElementById('editEmail').value = dados.email || '';
document.getElementById('editTelefone').value = dados.telefone || '';
document.getElementById('editEndereco').value = dados.endereco || '';
document.getElementById('editFiliado').checked = dados.filiado === 'True';
// Atualizar o título do modal com o nome do militante
const modalTitle = modalEditarMilitante.querySelector('.modal-title');
if (modalTitle) {
modalTitle.innerHTML = `<i class="fas fa-user-edit me-2"></i>Editar ${militanteNome}`;
}
// Configurar formulário
const form = document.getElementById('formEditarMilitante');
if (form) {
form.action = `/militantes/editar/${militanteId}`;
const idInput = document.getElementById('edit_militante_id');
if (idInput) {
idInput.value = militanteId;
}
console.log('Action do formulário:', form.action);
console.log('ID do militante no formulário:', militanteId);
} else {
console.error('Formulário não encontrado!');
return;
}
// Mostrar loading
const modalBody = modalEditarMilitante.querySelector('.modal-body');
if (modalBody) {
modalBody.style.opacity = '0.5';
}
// Buscar dados completos do militante via AJAX
console.log(`Fazendo requisição para /militantes/${militanteId}/dados`);
// Obter o CSRF token
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
console.log('CSRF token:', csrfToken);
fetch(`/militantes/${militanteId}/dados`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRFToken': csrfToken
},
credentials: 'same-origin'
})
.then(response => {
console.log('Resposta recebida:', response);
if (!response.ok) {
return response.json().then(data => {
throw new Error(data.message || `HTTP error! status: ${response.status}`);
});
}
return response.json();
})
.then(dados => {
console.log('Dados do militante recebidos:', dados);
if (!dados) {
throw new Error('Dados do militante não encontrados');
}
try {
console.log('Preenchendo dados básicos...');
// Dados Básicos
document.getElementById('edit_nome').value = dados.nome || '';
document.getElementById('edit_cpf').value = dados.cpf || '';
document.getElementById('edit_titulo_eleitoral').value = dados.titulo_eleitoral || '';
document.getElementById('edit_data_nascimento').value = dados.data_nascimento || '';
document.getElementById('edit_data_entrada').value = dados.data_entrada_oci || '';
document.getElementById('edit_data_efetivacao').value = dados.data_efetivacao_oci || '';
console.log('Dados básicos preenchidos');
console.log('Preenchendo dados de contato...');
// Contato
document.getElementById('edit_telefone1').value = dados.telefone1 || '';
document.getElementById('edit_telefone2').value = dados.telefone2 || '';
document.getElementById('edit_email').value = dados.email || '';
console.log('Dados de contato preenchidos');
console.log('Preenchendo dados de endereço...');
// Endereço
if (dados.endereco) {
document.getElementById('edit_cep').value = dados.endereco.cep || '';
document.getElementById('edit_estado').value = dados.endereco.estado || '';
document.getElementById('edit_cidade').value = dados.endereco.cidade || '';
document.getElementById('edit_bairro').value = dados.endereco.bairro || '';
document.getElementById('edit_logradouro').value = dados.endereco.rua || '';
document.getElementById('edit_numero').value = dados.endereco.numero || '';
document.getElementById('edit_complemento').value = dados.endereco.complemento || '';
}
console.log('Dados de endereço preenchidos');
console.log('Preenchendo dados profissionais...');
// Profissional
document.getElementById('edit_profissao').value = dados.profissao || '';
document.getElementById('edit_regime_trabalho').value = dados.regime_trabalho || '';
document.getElementById('edit_empresa').value = dados.empresa || '';
document.getElementById('edit_contratante').value = dados.contratante || '';
console.log('Dados profissionais preenchidos');
console.log('Preenchendo dados acadêmicos...');
// Acadêmico
document.getElementById('edit_instituicao_ensino').value = dados.instituicao_ensino || '';
document.getElementById('edit_tipo_instituicao').value = dados.tipo_instituicao || '';
console.log('Dados acadêmicos preenchidos');
console.log('Preenchendo dados sindicais...');
// Sindical
document.getElementById('edit_sindicato').value = dados.sindicato || '';
document.getElementById('edit_cargo_sindical').value = dados.cargo_sindical || '';
document.getElementById('edit_central_sindical').value = dados.central_sindical || '';
document.getElementById('edit_dirigente_sindical').checked = dados.dirigente_sindical || false;
console.log('Dados sindicais preenchidos');
console.log('Preenchendo dados organizacionais...');
// Organização
document.getElementById('edit_estado_militante').value = dados.estado || 'ATIVO';
document.getElementById('edit_celula').value = dados.celula_id || '';
// Responsabilidades
const responsabilidades = dados.responsabilidades || 0;
document.querySelectorAll('input[name="responsabilidades"]').forEach(checkbox => {
const valor = parseInt(checkbox.value);
checkbox.checked = (responsabilidades & valor) !== 0;
});
console.log('Dados organizacionais preenchidos');
console.log('Todos os dados preenchidos com sucesso!');
} catch (error) {
console.error('Erro ao preencher os campos:', error);
throw new Error('Erro ao preencher os dados do militante: ' + error.message);
}
})
.catch(error => {
console.error('Erro ao buscar dados do militante:', error);
alert('Erro ao carregar dados do militante: ' + error.message);
})
.finally(() => {
if (modalBody) {
modalBody.style.opacity = '1';
}
});
});
// Envio do formulário de edição via AJAX
@@ -163,53 +291,76 @@ document.addEventListener('DOMContentLoaded', function() {
if (formEditarMilitante) {
formEditarMilitante.addEventListener('submit', function(e) {
e.preventDefault();
console.log('Enviando formulário de edição...');
const formData = new FormData(this);
console.log('Dados do formulário:', Object.fromEntries(formData));
fetch(this.action, {
// Adicionar responsabilidades selecionadas
const responsabilidades = Array.from(this.querySelectorAll('input[name="responsabilidades"]:checked'))
.map(cb => parseInt(cb.value))
.reduce((a, b) => a + b, 0);
formData.set('responsabilidades', responsabilidades);
// Obter o CSRF token
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
console.log('CSRF token:', csrfToken);
// Obter o ID do militante do campo hidden
const militanteId = document.getElementById('edit_militante_id').value;
console.log('ID do militante:', militanteId);
if (!militanteId) {
console.error('ID do militante não encontrado!');
mostrarAlerta('Erro: ID do militante não encontrado', 'danger');
return;
}
// Garantir que o campo de endereço está correto
const logradouro = formData.get('logradouro');
if (logradouro) {
formData.set('rua', logradouro);
formData.delete('logradouro');
}
fetch(`/militantes/editar/${militanteId}`, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
'X-Requested-With': 'XMLHttpRequest',
'X-CSRFToken': csrfToken
},
credentials: 'same-origin'
})
.then(response => {
console.log('Resposta recebida:', response);
if (!response.ok) {
return response.json().then(data => {
throw new Error(data.message || `HTTP error! status: ${response.status}`);
});
}
return response.json();
})
.then(response => response.json())
.then(data => {
console.log('Resposta processada:', data);
if (data.status === 'success') {
// Fechar o modal
bootstrap.Modal.getInstance(modalEditarMilitante).hide();
// Atualizar a lista
location.reload();
// Mostrar mensagem de sucesso
const alertDiv = document.createElement('div');
alertDiv.className = 'alert alert-success alert-dismissible fade show';
alertDiv.innerHTML = `
${data.message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.querySelector('.container').insertBefore(alertDiv, document.querySelector('.container').firstChild);
mostrarAlerta(data.message, 'success');
// Recarregar a página após um breve delay
setTimeout(() => {
location.reload();
}, 1500);
} else {
// Mostrar erro
const alertDiv = document.createElement('div');
alertDiv.className = 'alert alert-danger alert-dismissible fade show';
alertDiv.innerHTML = `
${data.message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.querySelector('.modal-body').insertBefore(alertDiv, formEditarMilitante);
throw new Error(data.message || 'Erro ao salvar dados');
}
})
.catch(error => {
console.error('Erro:', error);
const alertDiv = document.createElement('div');
alertDiv.className = 'alert alert-danger alert-dismissible fade show';
alertDiv.innerHTML = `
Erro ao atualizar militante. Tente novamente.
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.querySelector('.modal-body').insertBefore(alertDiv, formEditarMilitante);
console.error('Erro ao enviar formulário:', error);
mostrarAlerta(`Erro ao salvar dados: ${error.message}`, 'danger');
});
});
}
@@ -334,32 +485,31 @@ document.addEventListener('DOMContentLoaded', function() {
}
// Máscara para CPF
const cpfInput = document.getElementById('cpf');
if (cpfInput) {
cpfInput.addEventListener('input', function(e) {
const cpfInputs = document.querySelectorAll('input[name="cpf"]');
cpfInputs.forEach(input => {
input.addEventListener('input', function(e) {
let value = e.target.value.replace(/\D/g, '');
if (value.length <= 11) {
value = value.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4');
value = value.replace(/(\d{3})(\d)/, '$1.$2');
value = value.replace(/(\d{3})(\d)/, '$1.$2');
value = value.replace(/(\d{3})(\d{1,2})$/, '$1-$2');
e.target.value = value;
}
});
}
});
// Máscara para telefone
const telefoneInput = document.getElementById('telefone');
if (telefoneInput) {
telefoneInput.addEventListener('input', function(e) {
const telefoneInputs = document.querySelectorAll('input[name="telefone1"], input[name="telefone2"]');
telefoneInputs.forEach(input => {
input.addEventListener('input', function(e) {
let value = e.target.value.replace(/\D/g, '');
if (value.length <= 11) {
if (value.length === 11) {
value = value.replace(/(\d{2})(\d{5})(\d{4})/, '($1) $2-$3');
} else {
value = value.replace(/(\d{2})(\d{4})(\d{4})/, '($1) $2-$3');
}
value = value.replace(/(\d{2})(\d)/, '($1) $2');
value = value.replace(/(\d{4,5})(\d{4})$/, '$1-$2');
e.target.value = value;
}
});
}
});
// Limpar formulário e alertas quando o modal for fechado
const modalNovoMilitante = document.getElementById('modalNovoMilitante');
@@ -444,4 +594,71 @@ document.addEventListener('DOMContentLoaded', function() {
document.body.removeChild(link);
});
}
});
// Configurar máscaras de input
// CEP
const cepInputs = document.querySelectorAll('input[name="cep"]');
cepInputs.forEach(input => {
input.addEventListener('input', function(e) {
let value = e.target.value.replace(/\D/g, '');
if (value.length <= 8) {
value = value.replace(/(\d{5})(\d)/, '$1-$2');
e.target.value = value;
}
});
// Buscar endereço pelo CEP
input.addEventListener('blur', function(e) {
const cep = e.target.value.replace(/\D/g, '');
if (cep.length === 8) {
fetch(`https://viacep.com.br/ws/${cep}/json/`)
.then(response => response.json())
.then(data => {
if (!data.erro) {
const form = input.closest('form');
form.querySelector('input[name="logradouro"]').value = data.logradouro;
form.querySelector('input[name="bairro"]').value = data.bairro;
form.querySelector('input[name="cidade"]').value = data.localidade;
form.querySelector('select[name="estado"]').value = data.uf;
}
});
}
});
});
// Título Eleitoral
const tituloInputs = document.querySelectorAll('input[name="titulo_eleitoral"]');
tituloInputs.forEach(input => {
input.addEventListener('input', function(e) {
let value = e.target.value.replace(/\D/g, '');
if (value.length <= 12) {
value = value.replace(/(\d{4})(\d)/, '$1 $2');
value = value.replace(/(\d{4})(\d)/, '$1 $2');
e.target.value = value;
}
});
});
});
// Função para mostrar alertas
function mostrarAlerta(mensagem, tipo) {
// Criar o elemento de alerta
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${tipo} alert-dismissible fade show position-fixed top-0 start-50 translate-middle-x mt-3`;
alertDiv.style.zIndex = '9999';
alertDiv.innerHTML = `
${mensagem}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
// Adicionar o alerta ao corpo do documento
document.body.appendChild(alertDiv);
// Configurar o Bootstrap alert
const bsAlert = new bootstrap.Alert(alertDiv);
// Remover o alerta após 3 segundos
setTimeout(() => {
bsAlert.close();
}, 3000);
}