Melhorias na lógica de ativação de badges e atualização de responsabilidades

This commit is contained in:
andersonid
2025-04-04 20:37:22 -03:00
committed by LS
parent 97711d30c7
commit b47c9efc21
5 changed files with 890 additions and 302 deletions

71
app.py
View File

@@ -297,7 +297,8 @@ def home():
total_assinaturas=total_assinaturas,
ultimos_militantes=ultimos_militantes,
ultimos_pagamentos=ultimos_pagamentos,
tipos_pagamento=tipos_pagamento)
tipos_pagamento=tipos_pagamento,
Militante=Militante)
except Exception as e:
print(f"Erro na página inicial: {e}")
import traceback
@@ -311,7 +312,8 @@ def home():
total_materiais=0,
total_assinaturas=0,
ultimos_militantes=[],
ultimos_pagamentos=[])
ultimos_pagamentos=[],
Militante=Militante)
finally:
db.close()
@@ -1057,12 +1059,24 @@ def editar_militante(militante_id):
if responsabilidades_json:
responsabilidades_lista = json.loads(responsabilidades_json)
valor_responsabilidades = 0
if 'Finanças' in responsabilidades_lista:
if 'Responsável de Finanças' in responsabilidades_lista:
valor_responsabilidades |= Militante.RESPONSAVEL_FINANCAS
if 'Imprensa' in responsabilidades_lista:
if 'Responsável de Imprensa' in responsabilidades_lista:
valor_responsabilidades |= Militante.RESPONSAVEL_IMPRENSA
if 'Quadro-Orientador' in responsabilidades_lista:
valor_responsabilidades |= Militante.QUADRO_ORIENTADOR
if 'Secretário' in responsabilidades_lista:
valor_responsabilidades |= Militante.SECRETARIO
if 'MPS' in responsabilidades_lista:
valor_responsabilidades |= Militante.MPS
if 'Tesoureiro' in responsabilidades_lista:
valor_responsabilidades |= Militante.TESOUREIRO
if 'MNS' in responsabilidades_lista:
valor_responsabilidades |= Militante.MNS
if 'Juventude' in responsabilidades_lista:
valor_responsabilidades |= Militante.JUVENTUDE
if 'Aspirante' in responsabilidades_lista:
valor_responsabilidades |= Militante.ASPIRANTE
militante.responsabilidades = valor_responsabilidades
else:
militante.responsabilidades = 0
@@ -1080,6 +1094,34 @@ def editar_militante(militante_id):
db.commit()
print("Alterações salvas com sucesso")
# Converter responsabilidades para lista de strings
responsabilidades = []
if militante.responsabilidades & Militante.RESPONSAVEL_FINANCAS:
responsabilidades.append('Responsável de Finanças')
if militante.responsabilidades & Militante.RESPONSAVEL_IMPRENSA:
responsabilidades.append('Responsável de Imprensa')
if militante.responsabilidades & Militante.QUADRO_ORIENTADOR:
responsabilidades.append('Quadro-Orientador')
if militante.responsabilidades & Militante.SECRETARIO:
responsabilidades.append('Secretário')
if militante.responsabilidades & Militante.MPS:
responsabilidades.append('MPS')
if militante.responsabilidades & Militante.TESOUREIRO:
responsabilidades.append('Tesoureiro')
if militante.responsabilidades & Militante.MNS:
responsabilidades.append('MNS')
if militante.responsabilidades & Militante.JUVENTUDE:
responsabilidades.append('Juventude')
if militante.responsabilidades & Militante.ASPIRANTE:
responsabilidades.append('Aspirante')
return jsonify({
'status': 'success',
'message': f'Militante {militante.nome} atualizado com sucesso!',
'responsabilidades': responsabilidades,
'responsabilidades_bits': militante.responsabilidades
})
return jsonify({
'status': 'success',
'message': f'Militante {militante.nome} atualizado com sucesso!'
@@ -1586,11 +1628,23 @@ def buscar_dados_militante(militante_id):
# Converter responsabilidades para lista de strings
responsabilidades = []
if militante.responsabilidades & Militante.RESPONSAVEL_FINANCAS:
responsabilidades.append('Finanças')
responsabilidades.append('Responsável de Finanças')
if militante.responsabilidades & Militante.RESPONSAVEL_IMPRENSA:
responsabilidades.append('Imprensa')
responsabilidades.append('Responsável de Imprensa')
if militante.responsabilidades & Militante.QUADRO_ORIENTADOR:
responsabilidades.append('Quadro-Orientador')
if militante.responsabilidades & Militante.SECRETARIO:
responsabilidades.append('Secretário')
if militante.responsabilidades & Militante.MPS:
responsabilidades.append('MPS')
if militante.responsabilidades & Militante.TESOUREIRO:
responsabilidades.append('Tesoureiro')
if militante.responsabilidades & Militante.MNS:
responsabilidades.append('MNS')
if militante.responsabilidades & Militante.JUVENTUDE:
responsabilidades.append('Juventude')
if militante.responsabilidades & Militante.ASPIRANTE:
responsabilidades.append('Aspirante')
return jsonify({
'nome': militante.nome,
@@ -1610,7 +1664,7 @@ def buscar_dados_militante(militante_id):
'rua': militante.endereco.rua if militante.endereco else None,
'numero': militante.endereco.numero if militante.endereco else None,
'complemento': militante.endereco.complemento if militante.endereco else None
} if militante.endereco else None,
},
'profissao': militante.profissao,
'regime_trabalho': militante.regime_trabalho,
'empresa': militante.empresa,
@@ -1621,8 +1675,9 @@ def buscar_dados_militante(militante_id):
'cargo_sindical': militante.cargo_sindical,
'central_sindical': militante.central_sindical,
'dirigente_sindical': militante.dirigente_sindical,
'estado': militante.estado.name if militante.estado else None,
'estado': militante.estado.value if militante.estado else None,
'celula_id': militante.celula_id,
'responsabilidades': responsabilidades,
'responsabilidades': responsabilidades
})
except Exception as e:

View File

@@ -8,6 +8,54 @@ let currentPage = 1;
let rowsPerPage = 20;
let totalRows = 0;
// Mapa de responsabilidades para valores numéricos
const RESPONSABILIDADES_MAP = {
'Secretario': 1,
'Tesoureiro': 2,
'Imprensa': 4,
'MNS': 8,
'MPS': 16,
'Juventude': 32,
'Quadro-Orientador': 64,
'Aspirante': 128,
'Responsavel de Financas': 256,
'Responsavel de Imprensa': 512
};
// Mapa reverso para converter valores em nomes
const RESPONSABILIDADES_REVERSE_MAP = {
1: 'Secretario',
2: 'Tesoureiro',
4: 'Imprensa',
8: 'MNS',
16: 'MPS',
32: 'Juventude',
64: 'Quadro-Orientador',
128: 'Aspirante',
256: 'Responsavel de Financas',
512: 'Responsavel de Imprensa'
};
// Função para validar data no formato DD/MM/YYYY
function validarData(data) {
if (!data) return true; // Campo vazio é válido
// Verifica o formato
if (!/^\d{2}\/\d{2}\/\d{4}$/.test(data)) return false;
// Extrai dia, mês e ano
const [dia, mes, ano] = data.split('/').map(Number);
// Cria um objeto Date
const dataObj = new Date(ano, mes - 1, dia);
// Verifica se a data é válida
return dataObj.getDate() === dia &&
dataObj.getMonth() === mes - 1 &&
dataObj.getFullYear() === ano &&
ano >= 1900 && ano <= 2100;
}
// Função para formatar data do formato ISO (YYYY-MM-DD) para DD/MM/YYYY
function formatarData(data) {
if (!data) return '';
@@ -18,7 +66,7 @@ function formatarData(data) {
// Função para converter data de DD/MM/YYYY para YYYY-MM-DD
function converterDataParaISO(data) {
if (!data) return '';
if (data.includes('-')) return data; // Já está no formato ISO
if (data.includes('-')) return data;
const [dia, mes, ano] = data.split('/');
return `${ano}-${mes}-${dia}`;
}
@@ -138,8 +186,21 @@ function filtrarMilitantes() {
// Filtro de responsabilidades
if (filtroResponsabilidade) {
const badges = row.querySelectorAll('.badge');
const responsabilidadeMap = {
'responsavel-financas': 'RFI',
'responsavel-imprensa': 'RIM',
'quadro-orientador': 'QOR',
'secretario': 'SEC',
'tesoureiro': 'TES',
'imprensa': 'IMP',
'mns': 'MNS',
'mps': 'MPS',
'juventude': 'JUV',
'aspirante': 'ASP'
};
const responsabilidadeTexto = responsabilidadeMap[filtroResponsabilidade];
const hasResponsabilidade = Array.from(badges).some(badge =>
badge.textContent.toLowerCase() === filtroResponsabilidade.toLowerCase()
badge.textContent.trim() === responsabilidadeTexto
);
if (!hasResponsabilidade) {
shouldShow = false;
@@ -157,6 +218,7 @@ function filtrarMilitantes() {
// Marcar linha como filtrada ou não
if (shouldShow) {
row.removeAttribute('data-filtered-out');
row.style.display = '';
} else {
row.setAttribute('data-filtered-out', '');
row.style.display = 'none';
@@ -173,35 +235,208 @@ function filtrarMilitantes() {
function configurarCampoData(campo) {
if (!campo) return;
// Criar um campo de texto para exibição
const campoTexto = document.createElement('input');
campoTexto.type = 'text';
campoTexto.className = campo.className;
campoTexto.placeholder = 'dd/mm/aaaa';
campoTexto.readOnly = true; // Evita edição manual
// Esconder o campo original mas manter ao lado
campo.type = 'date';
campo.style.position = 'absolute';
campo.style.left = '-9999px';
// Inserir o campo de texto antes do campo de data
campo.parentNode.insertBefore(campoTexto, campo);
// Se já tiver valor, formatar
if (campo.value) {
campoTexto.value = formatarData(campo.value);
// Remover campo de texto anterior se existir
const campoTextoExistente = campo.previousElementSibling;
if (campoTextoExistente && campoTextoExistente.classList.contains('campo-data-texto')) {
campoTextoExistente.remove();
}
// Quando o campo original mudar, atualizar o texto
// Configurar o campo de data
campo.type = 'date';
// Se já tiver valor, garantir que está no formato ISO
if (campo.value) {
campo.value = converterDataParaISO(campo.value);
}
// Quando o valor mudar, garantir formato ISO
campo.addEventListener('change', function() {
campoTexto.value = this.value ? formatarData(this.value) : '';
if (this.value) {
this.value = converterDataParaISO(this.value);
}
});
}
// Função para carregar os dados do militante no modal de edição
function carregarDadosMilitante(id) {
console.log('Carregando dados do militante:', id);
fetch(`/militantes/dados/${id}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.text().then(text => {
try {
return JSON.parse(text);
} catch (e) {
console.error('Erro ao fazer parse do JSON:', e);
console.log('Texto recebido:', text);
throw e;
}
});
})
.then(data => {
console.log('Dados recebidos:', data);
// Mapear campos básicos
const camposTexto = {
'edit_militante_id': data.id,
'edit_nome': data.nome || '',
'edit_cpf': data.cpf || '',
'edit_titulo_eleitoral': data.titulo_eleitoral || '',
'edit_email': data.email || '',
'edit_telefone1': data.telefone1 || '',
'edit_telefone2': data.telefone2 || '',
'edit_sindicato': data.sindicato || '',
'edit_cargo_sindical': data.cargo_sindical || '',
'edit_central_sindical': data.central_sindical || ''
};
// Preencher campos de texto simples
Object.entries(camposTexto).forEach(([id, valor]) => {
const campo = document.getElementById(id);
if (campo) {
campo.value = valor;
} else {
console.warn(`Campo não encontrado: ${id}`);
}
});
// Quando clicar no campo de texto, abrir o calendário
campoTexto.addEventListener('click', function() {
campo.showPicker();
// Tratar campos de seleção
const selects = {
'edit_estado_militante': data.estado || 'ATIVO',
'edit_celula': data.celula_id || ''
};
Object.entries(selects).forEach(([id, valor]) => {
const campo = document.getElementById(id);
if (campo) {
campo.value = valor;
} else {
console.warn(`Campo select não encontrado: ${id}`);
}
});
// Tratar campos de checkbox
const checkboxes = {
'edit_dirigente_sindical': data.dirigente_sindical || false
};
Object.entries(checkboxes).forEach(([id, valor]) => {
const campo = document.getElementById(id);
if (campo) {
campo.checked = !!valor;
} else {
console.warn(`Campo checkbox não encontrado: ${id}`);
}
});
// Tratar campos de data
const camposData = {
'edit_data_nascimento': data.data_nascimento,
'edit_data_entrada': data.data_entrada_oci,
'edit_data_efetivacao': data.data_efetivacao_oci
};
Object.entries(camposData).forEach(([id, valor]) => {
const campo = document.getElementById(id);
if (campo) {
// Se o valor existe e está em formato ISO, converter para DD/MM/AAAA
if (valor && typeof valor === 'string' && valor.includes('-')) {
campo.value = formatarData(valor);
} else {
campo.value = valor || '';
}
} else {
console.warn(`Campo data não encontrado: ${id}`);
}
});
// Converter responsabilidades
const responsabilidadesBits = data.responsabilidades_bits || data.responsabilidades || 0;
console.log('Responsabilidades bits:', responsabilidadesBits);
// Resetar todas as badges para o estado inicial
const badges = document.querySelectorAll('#modalEditarMilitante .badge-clickable');
badges.forEach(badge => {
// Remover todas as classes exceto 'badge' e 'badge-clickable'
const classesToKeep = ['badge', 'badge-clickable'];
badge.classList.forEach(cls => {
if (!classesToKeep.includes(cls)) {
badge.classList.remove(cls);
}
});
// Adicionar a classe bg-light como estado inicial
badge.classList.add('bg-light');
badge.classList.remove('active');
});
// Ativar as badges correspondentes às responsabilidades
badges.forEach(badge => {
const value = parseInt(badge.getAttribute('data-value'));
if ((responsabilidadesBits & value) === value) {
badge.classList.remove('bg-light');
badge.classList.add('active');
// Adicionar a classe original
const originalClass = badge.getAttribute('data-original-class');
if (originalClass) {
originalClass.split(' ').forEach(cls => {
badge.classList.add(cls);
});
}
}
});
// Atualizar o valor total das responsabilidades
const responsabilidadesInput = document.getElementById('responsabilidades_values');
if (responsabilidadesInput) {
responsabilidadesInput.value = responsabilidadesBits;
}
// Reinicializar os badges clicáveis
initBadgeClickable();
})
.catch(error => {
console.error('Erro ao carregar dados do militante:', error);
// Mostrar alerta de erro
mostrarAlerta('Erro ao carregar dados do militante: ' + error.message, 'danger');
});
}
// Função para mostrar alertas
function mostrarAlerta(mensagem, tipo = 'success') {
console.log('Mostrando alerta:', mensagem, tipo);
// Criar div de alerta se não existir
let alertPlaceholder = document.getElementById('alertPlaceholder');
if (!alertPlaceholder) {
alertPlaceholder = document.createElement('div');
alertPlaceholder.id = 'alertPlaceholder';
alertPlaceholder.style.position = 'fixed';
alertPlaceholder.style.top = '20px';
alertPlaceholder.style.right = '20px';
alertPlaceholder.style.zIndex = '9999';
document.body.appendChild(alertPlaceholder);
}
// Criar wrapper para o alerta
const wrapper = document.createElement('div');
wrapper.innerHTML = `
<div class="alert alert-${tipo} alert-dismissible fade show" role="alert">
${mensagem}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
`;
alertPlaceholder.appendChild(wrapper);
// Remover o alerta após 5 segundos
setTimeout(() => {
wrapper.firstElementChild.classList.remove('show');
setTimeout(() => wrapper.remove(), 150);
}, 5000);
}
// Configurar eventos quando o DOM estiver carregado
@@ -242,10 +477,10 @@ document.addEventListener('DOMContentLoaded', function() {
filtroAtual = 'todos';
filtroResponsabilidade = null;
filtroCelula = null;
} else if (['financas', 'imprensa', 'quadro-orientador'].includes(filter)) {
} else if (['responsavel-financas', 'responsavel-imprensa', 'quadro-orientador'].includes(filter)) {
filtroAtual = 'todos';
filtroResponsabilidade = filter === 'financas' ? 'Finanças' :
filter === 'imprensa' ? 'Imprensa' :
filtroResponsabilidade = filter === 'responsavel-financas' ? 'Responsável de Finanças' :
filter === 'responsavel-imprensa' ? 'Responsável de Imprensa' :
'Quadro-Orientador';
filtroCelula = null;
} else if (filter === 'celula') {
@@ -265,44 +500,32 @@ document.addEventListener('DOMContentLoaded', function() {
console.log('Configurando modal de edição...');
// Configurar máscara para campos de data
$('.date-mask').mask('00/00/0000', {
placeholder: 'DD/MM/AAAA',
clearIfNotMatch: true
});
// Validar campos de data quando perderem o foco
$('.date-mask').on('blur', function() {
const valor = $(this).val();
if (valor && !validarData(valor)) {
$(this).addClass('is-invalid');
if (!$(this).next('.invalid-feedback').length) {
$(this).after('<div class="invalid-feedback">Data inválida</div>');
}
} else {
$(this).removeClass('is-invalid');
$(this).next('.invalid-feedback').remove();
}
});
// Configurar campos de data em todos os modais
const modalNovoMilitante = document.getElementById('modalNovoMilitante');
const modalEditarMilitante = document.getElementById('modalEditarMilitante');
// Configurar campos quando o modal novo for aberto
if (modalNovoMilitante) {
modalNovoMilitante.addEventListener('show.bs.modal', function() {
// Configurar campos de data do modal novo
const camposData = this.querySelectorAll('input[type="date"]');
camposData.forEach(configurarCampoData);
});
// Limpar formulário e alertas quando o modal for fechado
modalNovoMilitante.addEventListener('hidden.bs.modal', function () {
formNovoMilitante.reset();
const alerts = this.querySelectorAll('.alert');
alerts.forEach(alert => alert.remove());
});
}
// Configurar campos quando o modal editar for aberto
if (modalEditarMilitante) {
modalEditarMilitante.addEventListener('show.bs.modal', function() {
// Configurar campos de data do modal editar
const camposData = this.querySelectorAll('input[type="date"]');
camposData.forEach(configurarCampoData);
});
}
// Configurar modal de edição
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);
// Configurar eventos quando o modal é mostrado
modalEditarMilitante.addEventListener('show.bs.modal', function(event) {
console.log('Modal sendo aberto...');
@@ -327,201 +550,126 @@ document.addEventListener('DOMContentLoaded', function() {
}
// Buscar dados do militante
fetch(`/militantes/dados/${militanteId}`, {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(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 {
// Preencher os campos do formulário
const campos = {
'edit_nome': dados.nome,
'edit_cpf': dados.cpf,
'edit_titulo_eleitoral': dados.titulo_eleitoral,
'edit_data_nascimento': dados.data_nascimento,
'edit_data_entrada': dados.data_entrada_oci,
'edit_data_efetivacao': dados.data_efetivacao_oci,
'edit_telefone1': dados.telefone1,
'edit_telefone2': dados.telefone2,
'edit_email': dados.email,
'edit_cep': dados.endereco?.cep,
'edit_estado': dados.endereco?.estado,
'edit_cidade': dados.endereco?.cidade,
'edit_bairro': dados.endereco?.bairro,
'edit_rua': dados.endereco?.rua,
'edit_numero': dados.endereco?.numero,
'edit_complemento': dados.endereco?.complemento,
'edit_profissao': dados.profissao,
'edit_regime_trabalho': dados.regime_trabalho,
'edit_empresa': dados.empresa,
'edit_contratante': dados.contratante,
'edit_instituicao_ensino': dados.instituicao_ensino,
'edit_tipo_instituicao': dados.tipo_instituicao,
'edit_sindicato': dados.sindicato,
'edit_cargo_sindical': dados.cargo_sindical,
'edit_central_sindical': dados.central_sindical,
'edit_estado_militante': dados.estado,
'edit_celula': dados.celula_id
};
// Preencher cada campo se ele existir
Object.entries(campos).forEach(([id, valor]) => {
const campo = document.getElementById(id);
if (campo) {
if (campo.type === 'date') {
// Para campos de data, atualizar também o campo de texto
campo.value = valor || '';
const campoTexto = campo.previousElementSibling;
if (campoTexto && campoTexto.type === 'text') {
campoTexto.value = valor ? formatarData(valor) : '';
}
} else {
campo.value = valor || '';
}
}
carregarDadosMilitante(militanteId);
});
// Checkbox de dirigente sindical
const checkDirigente = document.getElementById('edit_dirigente_sindical');
if (checkDirigente) {
checkDirigente.checked = dados.dirigente_sindical;
}
// Checkboxes de responsabilidades
const responsabilidadesMap = {
'Finanças': 'edit_resp_1',
'Imprensa': 'edit_resp_2',
'Quadro-Orientador': 'edit_resp_4'
};
console.log('Responsabilidades recebidas:', dados.responsabilidades);
// Resetar todos os checkboxes primeiro
Object.values(responsabilidadesMap).forEach(id => {
const checkbox = document.getElementById(id);
if (checkbox) {
checkbox.checked = false;
}
});
// Marcar os checkboxes baseado nas responsabilidades recebidas
if (Array.isArray(dados.responsabilidades)) {
dados.responsabilidades.forEach(resp => {
const checkboxId = responsabilidadesMap[resp];
if (checkboxId) {
const checkbox = document.getElementById(checkboxId);
if (checkbox) {
checkbox.checked = true;
console.log(`Marcando checkbox ${checkboxId} para responsabilidade ${resp}`);
}
}
});
}
console.log('Formulário preenchido com sucesso');
} catch (error) {
console.error('Erro ao preencher formulário:', error);
mostrarAlerta('Erro ao carregar dados do militante', 'danger');
}
})
.catch(error => {
console.error('Erro ao buscar dados:', error);
mostrarAlerta('Erro ao carregar dados do militante', 'danger');
});
});
// Envio do formulário de edição via AJAX
// Configurar formulário de edição
const formEditarMilitante = document.getElementById('formEditarMilitante');
if (formEditarMilitante) {
formEditarMilitante.addEventListener('submit', function(e) {
e.preventDefault();
console.log('Enviando formulário de edição...');
console.log('Formulário submetido');
// Os campos de data já estarão no formato ISO, não precisa converter
// Validar todas as datas antes do envio
const camposData = this.querySelectorAll('.date-mask');
let datasValidas = true;
// Coletar responsabilidades selecionadas
const responsabilidades = [];
if (document.getElementById('edit_resp_1').checked) responsabilidades.push('Finanças');
if (document.getElementById('edit_resp_2').checked) responsabilidades.push('Imprensa');
if (document.getElementById('edit_resp_4').checked) responsabilidades.push('Quadro-Orientador');
camposData.forEach(campo => {
const valor = campo.value;
if (valor && !validarData(valor)) {
datasValidas = false;
$(campo).addClass('is-invalid');
if (!$(campo).next('.invalid-feedback').length) {
$(campo).after('<div class="invalid-feedback">Data inválida</div>');
}
} else {
$(campo).removeClass('is-invalid');
$(campo).next('.invalid-feedback').remove();
}
});
if (!datasValidas) {
mostrarAlerta('Existem datas inválidas no formulário', 'danger');
return;
}
// Converter datas para formato ISO
camposData.forEach(campo => {
if (campo.value) {
campo.value = converterDataParaISO(campo.value);
console.log(`Data convertida para ISO: ${campo.name} = ${campo.value}`);
}
});
// Criar FormData com todos os campos
const formData = new FormData(this);
// Verificar se o input de responsabilidades existe
const responsabilidadesInput = document.getElementById('responsabilidades_values');
if (!responsabilidadesInput) {
mostrarAlerta('Erro: Campo de responsabilidades não encontrado', 'danger');
return;
}
// Converter responsabilidades para array antes de enviar
const responsabilidadesBits = parseInt(responsabilidadesInput.value) || 0;
const responsabilidades = [];
// Converter bits para nomes de responsabilidades
for (const [valor, nome] of Object.entries(RESPONSABILIDADES_REVERSE_MAP)) {
if ((responsabilidadesBits & parseInt(valor)) === parseInt(valor)) {
responsabilidades.push(nome);
}
}
// Substituir o valor numérico pelo array de responsabilidades
formData.set('responsabilidades', JSON.stringify(responsabilidades));
console.log('Responsabilidades enviadas:', responsabilidades);
// Log dos dados sendo enviados
console.log('Dados do formulário:');
for (let [key, value] of formData.entries()) {
console.log(`${key}: ${value}`);
}
// Obter o CSRF token
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
// Obter o ID do militante do campo hidden
// Obter o ID do militante
const militanteId = document.getElementById('edit_militante_id').value;
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');
}
// Enviar dados para o servidor
fetch(`/militantes/editar/${militanteId}`, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRFToken': csrfToken
},
credentials: 'same-origin'
body: formData
})
.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.text().then(text => {
try {
const jsonResponse = JSON.parse(text);
return jsonResponse;
} catch (e) {
console.error('Erro ao fazer parse do JSON:', e);
console.log('Texto recebido:', text);
throw new Error('Erro ao processar resposta do servidor');
}
});
}
return response.json();
})
.then(data => {
console.log('Resposta processada:', data);
if (data.status === 'success') {
console.log('Resposta do servidor:', data);
if (data.success) {
mostrarAlerta('Militante atualizado com sucesso!', 'success');
// Fechar o modal
bootstrap.Modal.getInstance(modalEditarMilitante).hide();
// Mostrar mensagem de sucesso
mostrarAlerta(data.message, 'success');
// Recarregar a página após um breve delay
setTimeout(() => {
location.reload();
}, 1500);
const modal = bootstrap.Modal.getInstance(document.getElementById('modalEditarMilitante'));
if (modal) {
modal.hide();
} else {
throw new Error(data.message || 'Erro ao salvar dados');
console.warn('Modal não encontrado');
}
// Recarregar a página após 1 segundo
setTimeout(() => window.location.reload(), 1000);
} else {
throw new Error(data.message || 'Erro ao atualizar militante');
}
})
.catch(error => {
console.error('Erro ao enviar formulário:', error);
mostrarAlerta(`Erro ao salvar dados: ${error.message}`, 'danger');
console.error('Erro:', error);
mostrarAlerta(error.message || 'Erro ao atualizar militante', 'danger');
});
});
}
@@ -535,13 +683,37 @@ document.addEventListener('DOMContentLoaded', function() {
console.error('Modal de edição não encontrado!');
}
// Envio do formulário via AJAX
// Ajustar o formulário de novo militante
const formNovoMilitante = document.getElementById('formNovoMilitante');
if (formNovoMilitante) {
formNovoMilitante.addEventListener('submit', function(e) {
e.preventDefault();
// Os campos de data já estarão no formato ISO, não precisa converter
// Validar todas as datas antes do envio
const camposData = this.querySelectorAll('.date-mask');
let datasValidas = true;
camposData.forEach(campo => {
const valor = campo.value;
if (valor && !validarData(valor)) {
datasValidas = false;
$(campo).addClass('is-invalid');
if (!$(campo).next('.invalid-feedback').length) {
$(campo).after('<div class="invalid-feedback">Data inválida</div>');
}
}
});
if (!datasValidas) {
return; // Não envia o formulário se houver datas inválidas
}
// Converter datas para formato ISO antes do envio
camposData.forEach(campo => {
if (campo.value) {
campo.value = converterDataParaISO(campo.value);
}
});
const formData = new FormData(this);
@@ -792,30 +964,82 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
// Inicializar tooltips do Bootstrap
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl, {
placement: 'top',
trigger: 'hover'
});
});
// Inicializar badges clicáveis
initBadgeClickable();
// Inicializar paginação
updateVisibleRows();
updatePagination();
});
// 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>
`;
// Função para inicializar as badges clicáveis
function initBadgeClickable() {
const badges = document.querySelectorAll('.badge-clickable');
const selectedValues = [];
// 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);
// Inicializar o array com os valores das badges que já estão ativas
badges.forEach(badge => {
if (badge.classList.contains('active')) {
const value = parseInt(badge.getAttribute('data-value'));
if (!selectedValues.includes(value)) {
selectedValues.push(value);
}
}
});
// Atualizar o input hidden com o valor total inicial
const totalValue = selectedValues.reduce((a, b) => a + b, 0);
document.getElementById('responsabilidades_values').value = totalValue;
// Adicionar evento de clique para cada badge
badges.forEach(badge => {
badge.addEventListener('click', function() {
const value = parseInt(this.getAttribute('data-value'));
const originalClass = this.getAttribute('data-original-class');
// Toggle do estado ativo
if (this.classList.contains('active')) {
// Desativar badge
this.classList.remove('active');
originalClass.split(' ').forEach(cls => {
this.classList.remove(cls);
});
this.classList.add('bg-light');
// Remover valor do array
const index = selectedValues.indexOf(value);
if (index > -1) {
selectedValues.splice(index, 1);
}
} else {
// Ativar badge
this.classList.add('active');
this.classList.remove('bg-light');
originalClass.split(' ').forEach(cls => {
this.classList.add(cls);
});
// Adicionar valor ao array
if (!selectedValues.includes(value)) {
selectedValues.push(value);
}
}
// Atualizar o input hidden com o novo valor total
const newTotal = selectedValues.reduce((a, b) => a + b, 0);
document.getElementById('responsabilidades_values').value = newTotal;
console.log('Valores selecionados:', selectedValues);
console.log('Valor total:', newTotal);
});
});
}

View File

@@ -1,5 +1,10 @@
{% extends "base.html" %}
{% block head %}
<!-- Bootstrap Datepicker CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datepicker/1.9.0/css/bootstrap-datepicker.min.css">
{% endblock %}
{% block title %}Militantes{% endblock %}
{% block content %}
@@ -39,9 +44,16 @@
<li><a class="dropdown-item" href="#" data-filter="todos">Todos</a></li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">Responsabilidades</h6></li>
<li><a class="dropdown-item" href="#" data-filter="financas">Finanças</a></li>
<li><a class="dropdown-item" href="#" data-filter="imprensa">Imprensa</a></li>
<li><a class="dropdown-item" href="#" data-filter="responsavel-financas">Responsável de Finanças</a></li>
<li><a class="dropdown-item" href="#" data-filter="responsavel-imprensa">Responsável de Imprensa</a></li>
<li><a class="dropdown-item" href="#" data-filter="quadro-orientador">Quadro-Orientador</a></li>
<li><a class="dropdown-item" href="#" data-filter="secretario">Secretário</a></li>
<li><a class="dropdown-item" href="#" data-filter="tesoureiro">Tesoureiro</a></li>
<li><a class="dropdown-item" href="#" data-filter="imprensa">Imprensa</a></li>
<li><a class="dropdown-item" href="#" data-filter="mns">MNS</a></li>
<li><a class="dropdown-item" href="#" data-filter="mps">MPS</a></li>
<li><a class="dropdown-item" href="#" data-filter="juventude">Juventude</a></li>
<li><a class="dropdown-item" href="#" data-filter="aspirante">Aspirante</a></li>
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">Célula</h6></li>
{% for celula in celulas %}
@@ -78,13 +90,34 @@
<td data-celula="{{ militante.celula.nome }}">{{ militante.celula.nome }}</td>
<td>
{% if militante.responsabilidades|bitwise_and(Militante.RESPONSAVEL_FINANCAS) %}
<span class="badge bg-primary">Finanças</span>
<span class="badge bg-primary" data-bs-toggle="tooltip" title="Responsável de Finanças">RFI</span>
{% endif %}
{% if militante.responsabilidades|bitwise_and(Militante.RESPONSAVEL_IMPRENSA) %}
<span class="badge bg-info">Imprensa</span>
<span class="badge bg-info" data-bs-toggle="tooltip" title="Responsável de Imprensa">RIM</span>
{% endif %}
{% if militante.responsabilidades|bitwise_and(Militante.QUADRO_ORIENTADOR) %}
<span class="badge bg-success">Quadro-Orientador</span>
<span class="badge bg-success" data-bs-toggle="tooltip" title="Quadro-Orientador">QOR</span>
{% endif %}
{% if militante.responsabilidades|bitwise_and(Militante.SECRETARIO) %}
<span class="badge bg-purple" data-bs-toggle="tooltip" title="Secretário">SEC</span>
{% endif %}
{% if militante.responsabilidades|bitwise_and(Militante.TESOUREIRO) %}
<span class="badge bg-indigo" data-bs-toggle="tooltip" title="Tesoureiro">TES</span>
{% endif %}
{% if militante.responsabilidades|bitwise_and(Militante.IMPRENSA) %}
<span class="badge bg-teal" data-bs-toggle="tooltip" title="Imprensa">IMP</span>
{% endif %}
{% if militante.responsabilidades|bitwise_and(Militante.MNS) %}
<span class="badge bg-pink" data-bs-toggle="tooltip" title="MNS">MNS</span>
{% endif %}
{% if militante.responsabilidades|bitwise_and(Militante.MPS) %}
<span class="badge bg-orange" data-bs-toggle="tooltip" title="MPS">MPS</span>
{% endif %}
{% if militante.responsabilidades|bitwise_and(Militante.JUVENTUDE) %}
<span class="badge bg-danger" data-bs-toggle="tooltip" title="Juventude">JUV</span>
{% endif %}
{% if militante.responsabilidades|bitwise_and(Militante.ASPIRANTE) %}
<span class="badge bg-dark" data-bs-toggle="tooltip" title="Aspirante">ASP</span>
{% endif %}
</td>
<td class="text-end">
@@ -162,11 +195,70 @@
{% endblock %}
{% block scripts %}
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- jQuery Mask Plugin -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.mask/1.14.16/jquery.mask.min.js"></script>
<!-- Nosso script -->
<script src="{{ url_for('static', filename='js/militantes.js') }}"></script>
{% endblock %}
{% block extra_css %}
<style>
/* Estilo para o botão Novo Militante */
.btn-primary {
background-color: var(--bs-danger);
border-color: var(--bs-danger);
}
.btn-primary:hover,
.btn-primary:focus,
.btn-primary:active {
background-color: var(--bs-danger-dark, #b02a37) !important;
border-color: var(--bs-danger-dark, #b02a37) !important;
}
/* Estilo para os switches */
.form-check-input {
background-color: #fff;
border-color: rgba(220, 53, 69, 0.5);
}
.form-check-input:checked {
background-color: var(--bs-danger);
border-color: var(--bs-danger);
}
.form-check-input:focus {
border-color: var(--bs-danger);
box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
}
.form-switch .form-check-input {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28220, 53, 69, 0.85%29'/%3e%3c/svg%3e");
background-position: left center;
border-radius: 2em;
transition: background-position .15s ease-in-out;
}
.form-switch .form-check-input:checked {
background-position: right center;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");
}
.form-check {
min-height: 1.5rem;
padding-left: 2.8em;
margin-bottom: 0.5rem;
}
.form-check-label {
cursor: pointer;
user-select: none;
}
/* Estilo para o backdrop com blur em todos os modais */
.modal-backdrop.show {
backdrop-filter: blur(8px);
@@ -238,7 +330,41 @@ th[data-sort].sort-desc i {
/* Estilo para badges */
.badge {
font-weight: 500;
padding: 0.5em 0.8em;
padding: 0.4em 0.6em;
font-size: 0.75rem;
margin-right: 0.3rem;
min-width: 2em;
text-align: center;
border-radius: 4px;
cursor: help;
}
.badge:last-child {
margin-right: 0;
}
/* Cores personalizadas para badges */
.bg-purple { background-color: #6f42c1 !important; color: white !important; }
.bg-teal { background-color: #20c997 !important; color: white !important; }
.bg-orange { background-color: #fd7e14 !important; color: white !important; }
.bg-indigo { background-color: #6610f2 !important; color: white !important; }
.bg-pink { background-color: #d63384 !important; color: white !important; }
/* Cores do Bootstrap que vamos usar */
.badge.bg-primary { background-color: #0d6efd !important; }
.badge.bg-info { background-color: #0dcaf0 !important; }
.badge.bg-success { background-color: #198754 !important; }
.badge.bg-danger { background-color: #dc3545 !important; }
.badge.bg-dark { background-color: #212529 !important; }
/* Tooltip personalizado */
.tooltip {
font-size: 0.875rem;
}
.tooltip .tooltip-inner {
padding: 0.5rem 0.75rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
/* Estilo para botões de ação */
@@ -294,5 +420,91 @@ th[data-sort].sort-desc i {
min-width: 120px;
}
}
/* Estilos personalizados para o Bootstrap Datepicker */
.datepicker {
padding: 4px;
border-radius: 4px;
border: 1px solid #dee2e6;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
font-size: 0.875rem;
background-color: white !important;
color: #212529 !important;
}
.datepicker table {
background-color: white !important;
}
.datepicker table tr td,
.datepicker table tr th {
text-align: center;
width: 30px;
height: 30px;
border-radius: 2px;
color: #212529 !important;
background-color: white !important;
}
.datepicker table tr td.day:hover,
.datepicker table tr td.focused {
background: #f8f9fa !important;
color: #212529 !important;
}
.datepicker table tr td.active,
.datepicker table tr td.active:hover {
background-color: var(--bs-primary) !important;
color: white !important;
}
.datepicker table tr td.today {
background-color: #e9ecef !important;
color: #212529 !important;
}
.datepicker .datepicker-switch,
.datepicker .prev,
.datepicker .next {
font-weight: normal;
padding: 4px;
color: #212529 !important;
background-color: white !important;
}
.datepicker .dow {
font-weight: normal;
padding: 4px;
color: #212529 !important;
background-color: white !important;
}
.datepicker-dropdown:after {
border-bottom-color: white !important;
}
.datepicker-dropdown.datepicker-orient-top:after {
border-top-color: white !important;
}
/* Estilo para os campos de data */
.datepicker-input {
background-color: white !important;
color: #212529 !important;
cursor: pointer;
}
.datepicker-clear-btn {
color: #6c757d !important;
background-color: white !important;
padding: 5px 10px;
font-size: 12px;
border-radius: 4px;
}
.datepicker-clear-btn:hover {
background-color: #f8f9fa !important;
color: #495057 !important;
}
</style>
{% endblock %}

View File

@@ -58,20 +58,20 @@
</div>
<div class="col-md-6 mb-3">
<label for="edit_data_nascimento" class="form-label">Data de Nascimento</label>
<input type="date" class="form-control" id="edit_data_nascimento" name="data_nascimento"
placeholder="dd/mm/aaaa">
<input type="text" class="form-control date-mask" id="edit_data_nascimento" name="data_nascimento"
placeholder="DD/MM/AAAA" maxlength="10">
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="edit_data_entrada" class="form-label">Data de Entrada OCI</label>
<input type="date" class="form-control" id="edit_data_entrada" name="data_entrada_oci"
placeholder="dd/mm/aaaa">
<input type="text" class="form-control date-mask" id="edit_data_entrada" name="data_entrada_oci"
placeholder="DD/MM/AAAA" maxlength="10">
</div>
<div class="col-md-6 mb-3">
<label for="edit_data_efetivacao" class="form-label">Data de Efetivação</label>
<input type="date" class="form-control" id="edit_data_efetivacao" name="data_efetivacao_oci"
placeholder="dd/mm/aaaa">
<input type="text" class="form-control date-mask" id="edit_data_efetivacao" name="data_efetivacao_oci"
placeholder="DD/MM/AAAA" maxlength="10">
</div>
</div>
</div>
@@ -232,26 +232,31 @@
</div>
</div>
<!-- Responsabilidades -->
<div class="mb-3">
<label class="form-label d-block">Responsabilidades</label>
<div class="row g-3">
<div class="col-md-4">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="edit_resp_1" name="responsabilidades" value="256">
<label class="form-check-label" for="edit_resp_1">Finanças</label>
</div>
</div>
<div class="col-md-4">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="edit_resp_2" name="responsabilidades" value="512">
<label class="form-check-label" for="edit_resp_2">Imprensa</label>
</div>
</div>
<div class="col-md-4">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="edit_resp_4" name="responsabilidades" value="64">
<label class="form-check-label" for="edit_resp_4">Quadro-Orientador</label>
</div>
<div class="row">
<div class="col-12">
<label class="form-label">Responsabilidades</label>
<div class="responsabilidades-container">
<input type="hidden" name="responsabilidades" id="responsabilidades_values" value="0">
<span class="badge badge-clickable" data-value="{{ Militante.SECRETARIO }}" data-original-class="bg-secondary" data-bs-toggle="tooltip" title="Clique para alternar">Secretário</span>
<span class="badge badge-clickable" data-value="{{ Militante.RESPONSAVEL_IMPRENSA }}" data-original-class="bg-info" data-bs-toggle="tooltip" title="Clique para alternar">Responsável de Imprensa</span>
<span class="badge badge-clickable" data-value="{{ Militante.IMPRENSA }}" data-original-class="bg-warning" data-bs-toggle="tooltip" title="Clique para alternar">Imprensa</span>
<span class="badge badge-clickable" data-value="{{ Militante.MPS }}" data-original-class="bg-warning" data-bs-toggle="tooltip" title="Clique para alternar">MPS</span>
<span class="badge badge-clickable" data-value="{{ Militante.QUADRO_ORIENTADOR }}" data-original-class="bg-success" data-bs-toggle="tooltip" title="Clique para alternar">Quadro-Orientador</span>
<span class="badge badge-clickable" data-value="{{ Militante.RESPONSAVEL_FINANCAS }}" data-original-class="bg-primary" data-bs-toggle="tooltip" title="Clique para alternar">Responsável de Finanças</span>
<span class="badge badge-clickable" data-value="{{ Militante.TESOUREIRO }}" data-original-class="bg-dark" data-bs-toggle="tooltip" title="Clique para alternar">Tesoureiro</span>
<span class="badge badge-clickable" data-value="{{ Militante.MNS }}" data-original-class="bg-info" data-bs-toggle="tooltip" title="Clique para alternar">MNS</span>
<span class="badge badge-clickable" data-value="{{ Militante.JUVENTUDE }}" data-original-class="bg-danger" data-bs-toggle="tooltip" title="Clique para alternar">Juventude</span>
<span class="badge badge-clickable" data-value="{{ Militante.ASPIRANTE }}" data-original-class="bg-light text-dark border" data-bs-toggle="tooltip" title="Clique para alternar">Aspirante</span>
</div>
</div>
</div>
@@ -268,3 +273,51 @@
</div>
</div>
</div>
<style>
.badge-clickable {
font-size: 0.9rem;
padding: 0.5rem 1rem;
margin: 0.3rem;
cursor: pointer;
transition: all 0.2s ease;
opacity: 0.7;
user-select: none;
background-color: #e9ecef !important;
color: #6c757d !important;
border: 1px solid #dee2e6;
}
.badge-clickable:hover {
opacity: 0.9;
}
.badge-clickable.active {
opacity: 1;
color: white !important;
}
.responsabilidades-container {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
padding: 1rem;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
background-color: #f8f9fa;
}
/* Cores personalizadas para badges */
.bg-purple { background-color: #6f42c1 !important; color: white !important; }
.bg-teal { background-color: #20c997 !important; color: white !important; }
.bg-orange { background-color: #fd7e14 !important; color: white !important; }
.bg-indigo { background-color: #6610f2 !important; color: white !important; }
.bg-pink { background-color: #d63384 !important; color: white !important; }
/* Cores do Bootstrap que vamos usar */
.active.bg-primary { background-color: #0d6efd !important; color: white !important; }
.active.bg-success { background-color: #198754 !important; color: white !important; }
.active.bg-info { background-color: #0dcaf0 !important; color: white !important; }
.active.bg-danger { background-color: #dc3545 !important; color: white !important; }
.active.bg-dark { background-color: #212529 !important; color: white !important; }
</style>

View File

@@ -57,20 +57,20 @@
</div>
<div class="col-md-6 mb-3">
<label for="data_nascimento" class="form-label">Data de Nascimento</label>
<input type="date" class="form-control" id="data_nascimento" name="data_nascimento"
placeholder="dd/mm/aaaa">
<input type="text" class="form-control date-mask" id="data_nascimento" name="data_nascimento"
placeholder="DD/MM/AAAA" maxlength="10">
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="data_entrada" class="form-label">Data de Entrada OCI</label>
<input type="date" class="form-control" id="data_entrada" name="data_entrada_oci"
placeholder="dd/mm/aaaa">
<input type="text" class="form-control date-mask" id="data_entrada" name="data_entrada_oci"
placeholder="DD/MM/AAAA" maxlength="10">
</div>
<div class="col-md-6 mb-3">
<label for="data_efetivacao" class="form-label">Data de Efetivação</label>
<input type="date" class="form-control" id="data_efetivacao" name="data_efetivacao_oci"
placeholder="dd/mm/aaaa">
<input type="text" class="form-control date-mask" id="data_efetivacao" name="data_efetivacao_oci"
placeholder="DD/MM/AAAA" maxlength="10">
</div>
</div>
</div>
@@ -230,19 +230,33 @@
</select>
</div>
</div>
<div class="mb-3">
<label class="form-label d-block">Responsabilidades</label>
<div class="row g-3">
{% for valor, nome in Militante.get_responsabilidades_list() %}
<div class="col-md-6">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="resp_{{ valor }}"
name="responsabilidades" value="{{ valor }}">
<label class="form-check-label" for="resp_{{ valor }}">{{ nome }}</label>
<div class="row">
<div class="col-12">
<label class="form-label">Responsabilidades</label>
<div class="responsabilidades-container">
<input type="hidden" name="responsabilidades" id="novo_responsabilidades_values" value="0">
<span class="badge badge-clickable bg-secondary" data-value="{{ Militante.SECRETARIO }}" data-bs-toggle="tooltip" title="Clique para alternar">Secretário</span>
<span class="badge badge-clickable bg-info" data-value="{{ Militante.RESPONSAVEL_IMPRENSA }}" data-bs-toggle="tooltip" title="Clique para alternar">Responsável de Imprensa</span>
<span class="badge badge-clickable bg-warning text-dark" data-value="{{ Militante.IMPRENSA }}" data-bs-toggle="tooltip" title="Clique para alternar">Imprensa</span>
<span class="badge badge-clickable bg-warning text-dark" data-value="{{ Militante.MPS }}" data-bs-toggle="tooltip" title="Clique para alternar">MPS</span>
<span class="badge badge-clickable bg-success" data-value="{{ Militante.QUADRO_ORIENTADOR }}" data-bs-toggle="tooltip" title="Clique para alternar">Quadro-Orientador</span>
<span class="badge badge-clickable bg-primary" data-value="{{ Militante.RESPONSAVEL_FINANCAS }}" data-bs-toggle="tooltip" title="Clique para alternar">Responsável de Finanças</span>
<span class="badge badge-clickable bg-dark" data-value="{{ Militante.TESOUREIRO }}" data-bs-toggle="tooltip" title="Clique para alternar">Tesoureiro</span>
<span class="badge badge-clickable bg-info" data-value="{{ Militante.MNS }}" data-bs-toggle="tooltip" title="Clique para alternar">MNS</span>
<span class="badge badge-clickable bg-danger" data-value="{{ Militante.JUVENTUDE }}" data-bs-toggle="tooltip" title="Clique para alternar">Juventude</span>
<span class="badge badge-clickable bg-light text-dark border" data-value="{{ Militante.ASPIRANTE }}" data-bs-toggle="tooltip" title="Clique para alternar">Aspirante</span>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
@@ -257,3 +271,33 @@
</div>
</div>
</div>
<style>
.badge-clickable {
font-size: 0.9rem;
padding: 0.5rem 1rem;
margin: 0.3rem;
cursor: pointer;
transition: all 0.2s ease;
opacity: 0.5;
user-select: none;
}
.badge-clickable:hover {
opacity: 0.8;
}
.badge-clickable.active {
opacity: 1;
}
.responsabilidades-container {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
padding: 1rem;
border: 1px solid #dee2e6;
border-radius: 0.375rem;
background-color: #f8f9fa;
}
</style>