From b47c9efc218c62000f68a64fb70987bea9b14afa Mon Sep 17 00:00:00 2001 From: andersonid Date: Fri, 4 Apr 2025 20:37:22 -0300 Subject: [PATCH] =?UTF-8?q?Melhorias=20na=20l=C3=B3gica=20de=20ativa=C3=A7?= =?UTF-8?q?=C3=A3o=20de=20badges=20e=20atualiza=C3=A7=C3=A3o=20de=20respon?= =?UTF-8?q?sabilidades?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 71 ++- static/js/militantes.js | 708 ++++++++++++++++--------- templates/listar_militantes.html | 226 +++++++- templates/modals/militante_editar.html | 107 +++- templates/modals/militante_novo.html | 80 ++- 5 files changed, 890 insertions(+), 302 deletions(-) diff --git a/app.py b/app.py index d97f0e3..24ea324 100644 --- a/app.py +++ b/app.py @@ -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: diff --git a/static/js/militantes.js b/static/js/militantes.js index 4994c04..e45117d 100644 --- a/static/js/militantes.js +++ b/static/js/militantes.js @@ -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 + // Remover campo de texto anterior se existir + const campoTextoExistente = campo.previousElementSibling; + if (campoTextoExistente && campoTextoExistente.classList.contains('campo-data-texto')) { + campoTextoExistente.remove(); + } + + // Configurar o campo de data 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 + // Se já tiver valor, garantir que está no formato ISO if (campo.value) { - campoTexto.value = formatarData(campo.value); + campo.value = converterDataParaISO(campo.value); } - // Quando o campo original mudar, atualizar o texto + // 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); - // Quando clicar no campo de texto, abrir o calendário - campoTexto.addEventListener('click', function() { - campo.showPicker(); - }); + 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}`); + } + }); + + // 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 = ` + + `; + 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('
Data inválida
'); + } + } 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 || ''; - } - } - }); - - // 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'); - }); + carregarDadosMilitante(militanteId); }); - // 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('
Data inválida
'); + } + } 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); - formData.set('responsabilidades', JSON.stringify(responsabilidades)); - console.log('Responsabilidades enviadas:', responsabilidades); - - // Obter o CSRF token - const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content'); - - // Obter o ID do militante do campo hidden - 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'); + // 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; } - // Garantir que o campo de endereço está correto - const logradouro = formData.get('logradouro'); - if (logradouro) { - formData.set('rua', logradouro); - formData.delete('logradouro'); + // 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)); + + // Log dos dados sendo enviados + console.log('Dados do formulário:'); + for (let [key, value] of formData.entries()) { + console.log(`${key}: ${value}`); + } + + // Obter o ID do militante + const militanteId = document.getElementById('edit_militante_id').value; + if (!militanteId) { + mostrarAlerta('Erro: ID do militante não encontrado', 'danger'); + return; + } + + // 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(); + const modal = bootstrap.Modal.getInstance(document.getElementById('modalEditarMilitante')); + if (modal) { + modal.hide(); + } else { + console.warn('Modal não encontrado'); + } - // Mostrar mensagem de sucesso - mostrarAlerta(data.message, 'success'); - - // Recarregar a página após um breve delay - setTimeout(() => { - location.reload(); - }, 1500); + // Recarregar a página após 1 segundo + setTimeout(() => window.location.reload(), 1000); } else { - throw new Error(data.message || 'Erro ao salvar dados'); + 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('
Data inválida
'); + } + } + }); + + 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} - - `; +// 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); + // 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); + } + } + }); - // Configurar o Bootstrap alert - const bsAlert = new bootstrap.Alert(alertDiv); + // Atualizar o input hidden com o valor total inicial + const totalValue = selectedValues.reduce((a, b) => a + b, 0); + document.getElementById('responsabilidades_values').value = totalValue; - // Remover o alerta após 3 segundos - setTimeout(() => { - bsAlert.close(); - }, 3000); -} \ No newline at end of file + // 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); + }); + }); +} \ No newline at end of file diff --git a/templates/listar_militantes.html b/templates/listar_militantes.html index 144c353..8e33eef 100644 --- a/templates/listar_militantes.html +++ b/templates/listar_militantes.html @@ -1,5 +1,10 @@ {% extends "base.html" %} +{% block head %} + + +{% endblock %} + {% block title %}Militantes{% endblock %} {% block content %} @@ -39,9 +44,16 @@
  • Todos
  • -
  • Finanças
  • -
  • Imprensa
  • +
  • Responsável de Finanças
  • +
  • Responsável de Imprensa
  • Quadro-Orientador
  • +
  • Secretário
  • +
  • Tesoureiro
  • +
  • Imprensa
  • +
  • MNS
  • +
  • MPS
  • +
  • Juventude
  • +
  • Aspirante
  • {% for celula in celulas %} @@ -78,13 +90,34 @@ {{ militante.celula.nome }} {% if militante.responsabilidades|bitwise_and(Militante.RESPONSAVEL_FINANCAS) %} - Finanças + RFI {% endif %} {% if militante.responsabilidades|bitwise_and(Militante.RESPONSAVEL_IMPRENSA) %} - Imprensa + RIM {% endif %} {% if militante.responsabilidades|bitwise_and(Militante.QUADRO_ORIENTADOR) %} - Quadro-Orientador + QOR + {% endif %} + {% if militante.responsabilidades|bitwise_and(Militante.SECRETARIO) %} + SEC + {% endif %} + {% if militante.responsabilidades|bitwise_and(Militante.TESOUREIRO) %} + TES + {% endif %} + {% if militante.responsabilidades|bitwise_and(Militante.IMPRENSA) %} + IMP + {% endif %} + {% if militante.responsabilidades|bitwise_and(Militante.MNS) %} + MNS + {% endif %} + {% if militante.responsabilidades|bitwise_and(Militante.MPS) %} + MPS + {% endif %} + {% if militante.responsabilidades|bitwise_and(Militante.JUVENTUDE) %} + JUV + {% endif %} + {% if militante.responsabilidades|bitwise_and(Militante.ASPIRANTE) %} + ASP {% endif %} @@ -162,11 +195,70 @@ {% endblock %} {% block scripts %} - + + + + + + + + {% endblock %} {% block extra_css %} {% endblock %} \ No newline at end of file diff --git a/templates/modals/militante_editar.html b/templates/modals/militante_editar.html index efbca5a..aa67a46 100644 --- a/templates/modals/militante_editar.html +++ b/templates/modals/militante_editar.html @@ -58,20 +58,20 @@
    - +
    - +
    - +
    @@ -232,26 +232,31 @@ -
    - -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    +
    +
    + +
    + + + Secretário + + Responsável de Imprensa + + Imprensa + + MPS + + Quadro-Orientador + + Responsável de Finanças + + Tesoureiro + + MNS + + Juventude + + Aspirante
    @@ -267,4 +272,52 @@
    -
    \ No newline at end of file + + + \ No newline at end of file diff --git a/templates/modals/militante_novo.html b/templates/modals/militante_novo.html index 91be6e7..d5af0c5 100644 --- a/templates/modals/militante_novo.html +++ b/templates/modals/militante_novo.html @@ -57,20 +57,20 @@
    - +
    - +
    - +
    @@ -230,18 +230,32 @@ -
    - -
    - {% for valor, nome in Militante.get_responsabilidades_list() %} -
    -
    - - -
    +
    +
    + +
    + + + Secretário + + Responsável de Imprensa + + Imprensa + + MPS + + Quadro-Orientador + + Responsável de Finanças + + Tesoureiro + + MNS + + Juventude + + Aspirante
    - {% endfor %}
    @@ -256,4 +270,34 @@
    - \ No newline at end of file + + + \ No newline at end of file