From abc46704c3ba504ed8e11aac10190ab2b493675a Mon Sep 17 00:00:00 2001 From: andersonid Date: Wed, 9 Apr 2025 09:20:07 -0300 Subject: [PATCH] =?UTF-8?q?Corrigir=20atualiza=C3=A7=C3=A3o=20de=20dados?= =?UTF-8?q?=20na=20tabela=20de=20militantes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 433 +++++---- static/css/components.css | 48 + static/css/styles.css | 53 + static/img/favicon.ico | 1 + static/js/forms.js | 87 +- static/js/militantes.js | 1185 +++++++++++++++-------- static/js/pagamentos.js | 66 +- static/js/table_sort.js | 200 ++++ static/js/testes.js | 284 ++++++ static/js/vendas.js | 119 +++ templates/base.html | 1 + templates/listar_militantes.html | 2 +- templates/listar_relatorios_cotas.html | 68 +- templates/listar_relatorios_vendas.html | 68 +- templates/militantes.html | 14 +- templates/modals/militante_editar.html | 548 ++++++----- templates/novo_relatorio_cotas.html | 104 +- templates/novo_relatorio_vendas.html | 104 +- utils/date_utils.py | 171 ++++ 19 files changed, 2582 insertions(+), 974 deletions(-) create mode 100644 static/css/styles.css create mode 100644 static/img/favicon.ico create mode 100644 static/js/table_sort.js create mode 100644 static/js/testes.js create mode 100644 static/js/vendas.js create mode 100644 utils/date_utils.py diff --git a/app.py b/app.py index 14ba226..45fbe4a 100644 --- a/app.py +++ b/app.py @@ -25,7 +25,7 @@ from functions.database import ( ) from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker, joinedload -from datetime import datetime, timedelta +from datetime import datetime, timedelta, date from flask_bootstrap import Bootstrap5 from functions.validations import validar_cpf from functools import wraps @@ -43,7 +43,6 @@ import qrcode import base64 from io import BytesIO from create_admin import create_admin_user -from create_test_users import create_test_users from werkzeug.security import generate_password_hash, check_password_hash from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user import random @@ -51,6 +50,7 @@ import string from sqlalchemy.sql import func from flask_wtf.csrf import CSRFProtect import json +from utils.date_utils import validar_data, converter_data, validar_sequencia_datas, calcular_idade load_dotenv() @@ -615,14 +615,17 @@ def create_app(): @require_login def novo_pagamento(): if request.method == "POST": - militante_id = request.form.get("militante_id") - tipo_pagamento_id = request.form.get("tipo_pagamento_id") - valor = float(request.form.get("valor")) - data_pagamento = datetime.strptime(request.form.get("data_pagamento"), "%Y-%m-%d").date() - - # Criar novo pagamento - db = get_db_connection() try: + militante_id = request.form.get("militante_id") + tipo_pagamento_id = request.form.get("tipo_pagamento_id") + valor = float(request.form.get("valor")) + data_pagamento = converter_data(request.form.get("data_pagamento")) + + if not validar_data(data_pagamento): + flash('Data de pagamento inválida ou futura', 'danger') + return redirect(url_for('novo_pagamento')) + + db = get_db_connection() pagamento = Pagamento( militante_id=militante_id, tipo_pagamento_id=tipo_pagamento_id, @@ -635,9 +638,9 @@ def create_app(): return redirect(url_for('listar_pagamentos')) except Exception as e: db.rollback() - print(f"Erro ao cadastrar pagamento: {e}") + app.logger.error(f"Erro ao cadastrar pagamento: {e}") flash('Erro ao cadastrar pagamento', 'danger') - return render_template("novo_pagamento.html") + return redirect(url_for('novo_pagamento')) finally: db.close() @@ -687,7 +690,7 @@ def create_app(): militante_id = request.form.get("militante_id") tipo_pagamento = request.form.get("tipo_pagamento") valor = float(request.form.get("valor")) - data_pagamento = datetime.strptime(request.form.get("data_pagamento"), "%Y-%m-%d").date() + data_pagamento = converter_data(request.form.get("data_pagamento")) db = get_db_connection() pagamento = Pagamento( @@ -718,7 +721,7 @@ def create_app(): tipo_material_id = request.form.get('tipo_material_id') descricao = request.form.get('descricao') valor = float(request.form.get('valor')) - data_venda = datetime.strptime(request.form.get('data_venda'), '%Y-%m-%d') + data_venda = converter_data(request.form.get('data_venda')) material = MaterialVendido( militante_id=militante_id, @@ -775,44 +778,51 @@ def create_app(): @require_login @require_permission(Permission.VIEW_CELL_REPORTS) def nova_venda_jornal(): - db = get_db_connection() - try: - militante_id = request.form.get('militante_id') - quantidade = int(request.form.get('quantidade')) - valor_total = float(request.form.get('valor_total')) - data_venda = datetime.strptime(request.form.get('data_venda'), '%Y-%m-%d') - - venda = VendaJornalAvulso( - militante_id=militante_id, - quantidade=quantidade, - valor_total=valor_total, - data_venda=data_venda - ) - - db.add(venda) - db.commit() - - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return jsonify({ - 'status': 'success', - 'message': 'Venda cadastrada com sucesso!' - }) - - flash('Venda cadastrada com sucesso!', 'success') - return redirect(url_for('listar_vendas_jornal')) - - except Exception as e: - db.rollback() - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return jsonify({ - 'status': 'error', - 'message': 'Erro ao cadastrar venda. Por favor, tente novamente.' - }), 400 - - flash('Erro ao cadastrar venda. Por favor, tente novamente.', 'error') - return redirect(url_for('listar_vendas_jornal')) - finally: - db.close() + if request.method == "POST": + try: + militante_id = request.form.get('militante_id') + quantidade = int(request.form.get('quantidade')) + valor_total = float(request.form.get('valor_total')) + data_venda = converter_data(request.form.get('data_venda')) + + if not validar_data(data_venda): + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return jsonify({ + 'status': 'error', + 'message': 'Data de venda inválida ou futura' + }), 400 + flash('Data de venda inválida ou futura', 'danger') + return redirect(url_for('nova_venda_jornal')) + + db = get_db_connection() + venda = VendaJornalAvulso( + militante_id=militante_id, + quantidade=quantidade, + valor_total=valor_total, + data_venda=data_venda + ) + db.add(venda) + db.commit() + + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return jsonify({ + 'status': 'success', + 'message': 'Venda cadastrada com sucesso!' + }) + flash('Venda cadastrada com sucesso!', 'success') + return redirect(url_for('listar_vendas_jornal')) + except Exception as e: + db.rollback() + app.logger.error(f"Erro ao cadastrar venda: {e}") + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return jsonify({ + 'status': 'error', + 'message': 'Erro ao cadastrar venda' + }), 400 + flash('Erro ao cadastrar venda', 'danger') + return redirect(url_for('nova_venda_jornal')) + finally: + db.close() # Rota para listar vendas de jornal @app.route("/jornais") @@ -835,36 +845,56 @@ def create_app(): @require_permission(Permission.VIEW_CELL_REPORTS) def novo_relatorio_cotas(): if request.method == "POST": - setor_id = request.form.get("setor_id") - comite_id = request.form.get("comite_id") - total_cotas = float(request.form.get("total_cotas")) - data_relatorio = datetime.strptime(request.form.get("data_relatorio"), "%Y-%m-%d").date() - - db = get_db_connection() try: - relatorio_cotas_mensais = RelatorioCotasMensais( - setor_id=setor_id, - comite_id=comite_id, - total_cotas=total_cotas, - data_relatorio=data_relatorio - ) - db.add(relatorio_cotas_mensais) - db.commit() - flash('Relatório de cotas cadastrado com sucesso!', 'success') - return redirect(url_for('listar_relatorios_cotas')) - except Exception as e: - db.rollback() - print(f"Erro ao cadastrar relatório de cotas: {e}") - flash('Erro ao cadastrar relatório de cotas', 'danger') + setor_id = request.form.get("setor_id") + comite_id = request.form.get("comite_id") + total_cotas = float(request.form.get("total_cotas")) + data_relatorio = request.form.get("data_relatorio") + + # Validar data + if not validar_data(data_relatorio): + flash('Data do relatório inválida', 'danger') + return render_template("novo_relatorio_cotas.html") + + # Converter data + data_relatorio = converter_data(data_relatorio) + + # Validar data futura + if data_relatorio > date.today(): + flash('A data do relatório não pode ser futura', 'danger') + return render_template("novo_relatorio_cotas.html") + + db = get_db_connection() + try: + relatorio_cotas_mensais = RelatorioCotasMensais( + setor_id=setor_id, + comite_id=comite_id, + total_cotas=total_cotas, + data_relatorio=data_relatorio + ) + db.add(relatorio_cotas_mensais) + db.commit() + flash('Relatório de cotas cadastrado com sucesso!', 'success') + return redirect(url_for('listar_relatorios_cotas')) + except Exception as e: + db.rollback() + app.logger.error(f"Erro ao cadastrar relatório de cotas: {e}") + flash('Erro ao cadastrar relatório de cotas', 'danger') + return render_template("novo_relatorio_cotas.html") + finally: + db.close() + except ValueError as e: + flash(str(e), 'danger') return render_template("novo_relatorio_cotas.html") - finally: - db.close() db = get_db_connection() try: setores = db.query(Setor).order_by(Setor.nome).all() comites = db.query(ComiteCentral).order_by(ComiteCentral.nome).all() - return render_template("novo_relatorio_cotas.html", setores=setores, comites=comites) + return render_template("novo_relatorio_cotas.html", + setores=setores, + comites=comites, + hoje=date.today().strftime('%Y-%m-%d')) finally: db.close() @@ -890,36 +920,56 @@ def create_app(): @require_permission(Permission.VIEW_CELL_REPORTS) def novo_relatorio_vendas(): if request.method == "POST": - setor_id = request.form.get("setor_id") - comite_id = request.form.get("comite_id") - total_vendas = float(request.form.get("total_vendas")) - data_relatorio = datetime.strptime(request.form.get("data_relatorio"), "%Y-%m-%d").date() - - db = get_db_connection() try: - relatorio_vendas_materiais = RelatorioVendasMateriais( - setor_id=setor_id, - comite_id=comite_id, - total_vendas=total_vendas, - data_relatorio=data_relatorio - ) - db.add(relatorio_vendas_materiais) - db.commit() - flash('Relatório de vendas cadastrado com sucesso!', 'success') - return redirect(url_for('listar_relatorios_vendas')) - except Exception as e: - db.rollback() - print(f"Erro ao cadastrar relatório de vendas: {e}") - flash('Erro ao cadastrar relatório de vendas', 'danger') + setor_id = request.form.get("setor_id") + comite_id = request.form.get("comite_id") + total_vendas = float(request.form.get("total_vendas")) + data_relatorio = request.form.get("data_relatorio") + + # Validar data + if not validar_data(data_relatorio): + flash('Data do relatório inválida', 'danger') + return render_template("novo_relatorio_vendas.html") + + # Converter data + data_relatorio = converter_data(data_relatorio) + + # Validar data futura + if data_relatorio > date.today(): + flash('A data do relatório não pode ser futura', 'danger') + return render_template("novo_relatorio_vendas.html") + + db = get_db_connection() + try: + relatorio_vendas_materiais = RelatorioVendasMateriais( + setor_id=setor_id, + comite_id=comite_id, + total_vendas=total_vendas, + data_relatorio=data_relatorio + ) + db.add(relatorio_vendas_materiais) + db.commit() + flash('Relatório de vendas cadastrado com sucesso!', 'success') + return redirect(url_for('listar_relatorios_vendas')) + except Exception as e: + db.rollback() + app.logger.error(f"Erro ao cadastrar relatório de vendas: {e}") + flash('Erro ao cadastrar relatório de vendas', 'danger') + return render_template("novo_relatorio_vendas.html") + finally: + db.close() + except ValueError as e: + flash(str(e), 'danger') return render_template("novo_relatorio_vendas.html") - finally: - db.close() db = get_db_connection() try: setores = db.query(Setor).order_by(Setor.nome).all() comites = db.query(ComiteCentral).order_by(ComiteCentral.nome).all() - return render_template("novo_relatorio_vendas.html", setores=setores, comites=comites) + return render_template("novo_relatorio_vendas.html", + setores=setores, + comites=comites, + hoje=date.today().strftime('%Y-%m-%d')) finally: db.close() @@ -945,8 +995,8 @@ def create_app(): @require_permission('gerenciar_militantes') def editar_militante(militante_id): try: - # Verificar se o militante existe - militante = db_session.query(Militante).get(militante_id) + db = get_db_connection() + militante = db.query(Militante).get(militante_id) if not militante: return jsonify({ 'status': 'error', @@ -964,15 +1014,29 @@ def create_app(): telefone2 = request.form.get('telefone2') email = request.form.get('email') - # Converter datas para objetos date - data_nascimento = datetime.strptime(data_nascimento, '%Y-%m-%d').date() if data_nascimento else None - data_entrada_oci = datetime.strptime(data_entrada_oci, '%Y-%m-%d').date() if data_entrada_oci else None - data_efetivacao_oci = datetime.strptime(data_efetivacao_oci, '%Y-%m-%d').date() if data_efetivacao_oci else None + # Validar e converter datas + try: + data_nascimento = converter_data(data_nascimento) if data_nascimento else None + data_entrada_oci = converter_data(data_entrada_oci) if data_entrada_oci else None + data_efetivacao_oci = converter_data(data_efetivacao_oci) if data_efetivacao_oci else None + + # Validar sequência lógica das datas + validar_sequencia_datas( + data_nascimento=data_nascimento, + data_entrada=data_entrada_oci, + data_efetivacao=data_efetivacao_oci + ) + + except ValueError as e: + return jsonify({ + 'status': 'error', + 'message': str(e) + }), 400 # Atualizar dados básicos - militante.nome = nome - militante.cpf = cpf - militante.titulo_eleitoral = titulo_eleitoral + if nome: militante.nome = nome + if cpf: militante.cpf = cpf + if titulo_eleitoral: militante.titulo_eleitoral = titulo_eleitoral militante.data_nascimento = data_nascimento militante.data_entrada_oci = data_entrada_oci militante.data_efetivacao_oci = data_efetivacao_oci @@ -981,70 +1045,45 @@ def create_app(): # Atualizar email if email: - # Verificar se já existe um email para este militante - email_existente = db_session.query(EmailMilitante).filter_by(militante_id=militante_id).first() + # Verificar se já existe email + email_existente = db.query(EmailMilitante).filter_by(militante_id=militante_id).first() if email_existente: email_existente.endereco_email = email else: novo_email = EmailMilitante(endereco_email=email, militante_id=militante_id) - db_session.add(novo_email) + db.add(novo_email) - # Atualizar endereço - endereco = militante.endereco or Endereco(militante_id=militante_id) - endereco.cep = request.form.get('cep') - endereco.estado = request.form.get('estado') - endereco.cidade = request.form.get('cidade') - endereco.bairro = request.form.get('bairro') - endereco.rua = request.form.get('rua') - endereco.numero = request.form.get('numero') - endereco.complemento = request.form.get('complemento') + # Calcular idade + militante.idade = calcular_idade(data_nascimento) if data_nascimento else None - if not militante.endereco: - militante.endereco = endereco - db_session.add(endereco) - - # Atualizar dados profissionais - militante.empresa = request.form.get('empresa') - militante.contratante = request.form.get('contratante') - militante.instituicao_ensino = request.form.get('instituicao_ensino') - militante.tipo_instituicao = request.form.get('tipo_instituicao') - - # Atualizar dados sindicais - militante.sindicato = request.form.get('sindicato') - militante.cargo_sindical = request.form.get('cargo_sindical') - militante.central_sindical = request.form.get('central_sindical') - militante.dirigente_sindical = request.form.get('dirigente_sindical') == 'true' - - # Atualizar responsabilidades try: - responsabilidades_valor = request.form.get('responsabilidades_valor') - if responsabilidades_valor: - militante.responsabilidades = int(responsabilidades_valor) - print(f"Responsabilidades atualizadas para: {militante.responsabilidades}") - else: - militante.responsabilidades = 0 - print("Nenhuma responsabilidade definida") - except (ValueError, TypeError) as e: - print(f"Erro ao processar responsabilidades: {e}") - militante.responsabilidades = 0 - - # Salvar alterações - db_session.commit() - print("Alterações salvas com sucesso!") - - # Retornar resposta - return jsonify({ - 'status': 'success', - 'message': 'Militante atualizado com sucesso!', - 'responsabilidades': militante.get_responsabilidades() - }) + db.commit() + return jsonify({ + 'status': 'success', + 'message': 'Militante atualizado com sucesso', + 'data': { + 'nome': militante.nome, + 'cpf': militante.cpf, + 'idade': militante.idade, + 'emails': [e.endereco_email for e in militante.emails], + 'telefone1': militante.telefone1, + 'celula_id': str(militante.celula_id) if militante.celula_id else None, + 'responsabilidades_valor': militante.responsabilidades + } + }) + except Exception as e: + db.rollback() + app.logger.error(f"Erro ao salvar militante: {e}") + return jsonify({ + 'status': 'error', + 'message': 'Erro ao salvar alterações no banco de dados' + }), 500 except Exception as e: - db_session.rollback() - print(f"Erro ao salvar alterações: {e}") + app.logger.error(f"Erro ao editar militante: {e}") return jsonify({ 'status': 'error', - 'message': str(e) + 'message': 'Erro interno do servidor' }), 500 # Rota para criar um novo usuário @@ -1521,64 +1560,64 @@ def create_app(): @require_login @require_permission('gerenciar_militantes') def buscar_dados_militante(militante_id): - """Retorna os dados de um militante""" + """Busca os dados de um militante específico""" db = get_db_connection() try: - militante = db.query(Militante).options( - joinedload(Militante.endereco), - joinedload(Militante.emails) - ).get(militante_id) - + militante = db.query(Militante).get(militante_id) if not militante: + print(f"Militante não encontrado: ID {militante_id}") return jsonify({ 'status': 'error', 'message': 'Militante não encontrado' }), 404 - # Preparar dados do militante - dados = { + # Função auxiliar para formatar data com validação + def formatar_data_segura(data): + try: + if not data: + return None + return data.strftime('%Y-%m-%d') + except Exception as e: + print(f"Erro ao formatar data: {str(e)}, valor: {data}") + return None + + # Formatar datas com validação + data_nascimento = formatar_data_segura(militante.data_nascimento) + data_entrada_oci = formatar_data_segura(militante.data_entrada_oci) + data_efetivacao_oci = formatar_data_segura(militante.data_efetivacao_oci) + + print(f"Dados do militante {militante_id} recuperados com sucesso") + print(f"Datas formatadas: nascimento={data_nascimento}, entrada={data_entrada_oci}, efetivação={data_efetivacao_oci}") + + return jsonify({ + 'status': 'success', 'id': militante.id, 'nome': militante.nome, 'cpf': militante.cpf, 'titulo_eleitoral': militante.titulo_eleitoral, - 'data_nascimento': militante.data_nascimento.strftime('%Y-%m-%d') if militante.data_nascimento else None, - 'data_entrada_oci': militante.data_entrada_oci.strftime('%Y-%m-%d') if militante.data_entrada_oci else None, - 'data_efetivacao_oci': militante.data_efetivacao_oci.strftime('%Y-%m-%d') if militante.data_efetivacao_oci else None, + 'data_nascimento': data_nascimento, + 'data_entrada_oci': data_entrada_oci, + 'data_efetivacao_oci': data_efetivacao_oci, + 'emails': [email.endereco_email for email in militante.emails] if militante.emails else [], 'telefone1': militante.telefone1, 'telefone2': militante.telefone2, - 'emails': [email.endereco_email for email in militante.emails], - 'profissao': militante.profissao, - 'regime_trabalho': militante.regime_trabalho, - 'empresa': militante.empresa, - 'contratante': militante.contratante, - 'instituicao_ensino': militante.instituicao_ensino, - 'tipo_instituicao': militante.tipo_instituicao, + 'celula_id': militante.celula_id, + 'responsabilidades_valor': militante.responsabilidades, 'sindicato': militante.sindicato, 'cargo_sindical': militante.cargo_sindical, - 'dirigente_sindical': militante.dirigente_sindical, 'central_sindical': militante.central_sindical, - 'celula_id': militante.celula_id, - 'estado': militante.estado.name if militante.estado else None, - 'responsabilidades_valor': militante.responsabilidades, - 'endereco': { - 'cep': militante.endereco.cep if militante.endereco else None, - '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, - 'bairro': militante.endereco.bairro if militante.endereco else None, - 'cidade': militante.endereco.cidade if militante.endereco else None, - 'estado': militante.endereco.estado if militante.endereco else None - } - } - - return jsonify(dados) - + 'dirigente_sindical': militante.dirigente_sindical + }) except Exception as e: - print(f"Erro ao buscar dados do militante: {str(e)}") + import traceback + print(f"Erro ao buscar dados do militante {militante_id}:") + print(traceback.format_exc()) return jsonify({ 'status': 'error', - 'message': 'Erro ao buscar dados do militante' + 'message': f'Erro ao buscar dados do militante: {str(e)}' }), 500 + finally: + db.close() return app @@ -1624,7 +1663,7 @@ def init_system(): print("OTP: Nova configuração gerada") else: # Criar usuário de teste se não existir - create_test_users() + create_admin_user() finally: db.close() diff --git a/static/css/components.css b/static/css/components.css index 6fb45ac..31c632a 100644 --- a/static/css/components.css +++ b/static/css/components.css @@ -560,4 +560,52 @@ input.btn-secondary:hover, border-color: #0b5ed7 !important; color: white !important; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important; +} + +/* Estilos para alertas */ +.alert { + position: fixed; + top: 1rem; + left: 50%; + transform: translateX(-50%); + z-index: 9999; + min-width: 300px; + max-width: 600px; + text-align: center; + padding: 1rem 2.5rem 1rem 1rem; + margin: 0; + border-radius: 0.5rem; + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); +} + +.alert .btn-close { + position: absolute; + top: 50%; + right: 1rem; + transform: translateY(-50%); + padding: 0.5rem; +} + +.alert-success { + color: #0f5132; + background-color: #d1e7dd; + border-color: #badbcc; +} + +.alert-danger { + color: #842029; + background-color: #f8d7da; + border-color: #f5c2c7; +} + +.alert-warning { + color: #664d03; + background-color: #fff3cd; + border-color: #ffecb5; +} + +.alert-info { + color: #055160; + background-color: #cff4fc; + border-color: #b6effb; } \ No newline at end of file diff --git a/static/css/styles.css b/static/css/styles.css new file mode 100644 index 0000000..7680ca6 --- /dev/null +++ b/static/css/styles.css @@ -0,0 +1,53 @@ +/* Estilos globais para alertas do sistema */ +.alert { + position: relative; + margin-bottom: 1rem; +} + +/* Estilo base para o botão de fechar */ +.alert .btn-close { + filter: none; + opacity: 1; +} + +/* Alert Success */ +.alert-success .btn-close { + background: transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23198754'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat; +} + +/* Alert Danger */ +.alert-danger .btn-close { + background: transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23842029'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat; +} + +/* Alert Warning */ +.alert-warning .btn-close { + background: transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23997404'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat; +} + +/* Alert Info */ +.alert-info .btn-close { + background: transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23055160'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat; +} + +/* Efeito hover para todos os botões de fechar */ +.alert .btn-close:hover { + opacity: 0.75; +} + +/* Estilo das abas do modal */ +.nav-tabs .nav-link { + /* remover estilos */ +} + +.nav-tabs .nav-link.active { + /* remover estilos */ +} + +.nav-tabs .nav-link:hover:not(.active) { + /* remover estilos */ +} + +.nav-tabs .nav-link i { + /* remover estilos */ +} \ No newline at end of file diff --git a/static/img/favicon.ico b/static/img/favicon.ico new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/static/img/favicon.ico @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/js/forms.js b/static/js/forms.js index f44128c..86d03fc 100644 --- a/static/js/forms.js +++ b/static/js/forms.js @@ -106,30 +106,87 @@ document.addEventListener('DOMContentLoaded', function() { }); // Validação de datas - const dateInputs = document.querySelectorAll('input[type="date"]'); + const dateInputs = document.querySelectorAll('input[type="date"], input.date-mask'); dateInputs.forEach(input => { input.addEventListener('change', function() { - const date = new Date(this.value); - const today = new Date(); + console.log('Validando data:', this.value); - if (this.hasAttribute('min')) { - const minDate = new Date(this.getAttribute('min')); - if (date < minDate) { - this.setCustomValidity(`A data não pode ser anterior a ${minDate.toLocaleDateString()}`); - this.classList.add('is-invalid'); - return; + let dataValida = true; + let mensagemErro = ''; + + // Se for um campo com máscara, validar o formato + if (this.classList.contains('date-mask')) { + if (!validarData(this.value)) { + dataValida = false; + mensagemErro = 'Por favor, insira uma data válida no formato DD/MM/AAAA'; + } + } else { + // Para campos type="date", converter para Date + const date = new Date(this.value); + if (isNaN(date.getTime())) { + dataValida = false; + mensagemErro = 'Data inválida'; } } - if (this.hasAttribute('max')) { - const maxDate = new Date(this.getAttribute('max')); - if (date > maxDate) { - this.setCustomValidity(`A data não pode ser posterior a ${maxDate.toLocaleDateString()}`); - this.classList.add('is-invalid'); - return; + // Validar limites de data + if (dataValida) { + const hoje = new Date(); + hoje.setHours(0, 0, 0, 0); + + let dataComparacao; + if (this.classList.contains('date-mask')) { + const [dia, mes, ano] = this.value.split('/').map(Number); + dataComparacao = new Date(ano, mes - 1, dia); + } else { + dataComparacao = new Date(this.value); + } + + // Verificar data mínima + if (this.hasAttribute('min')) { + const minDate = new Date(this.getAttribute('min')); + if (dataComparacao < minDate) { + dataValida = false; + mensagemErro = `A data não pode ser anterior a ${minDate.toLocaleDateString()}`; + } + } + + // Verificar data máxima + if (this.hasAttribute('max')) { + const maxDate = new Date(this.getAttribute('max')); + if (dataComparacao > maxDate) { + dataValida = false; + mensagemErro = `A data não pode ser posterior a ${maxDate.toLocaleDateString()}`; + } + } + + // Verificar se é data futura (quando não permitido) + if (this.hasAttribute('data-no-future') && dataComparacao > hoje) { + dataValida = false; + mensagemErro = 'A data não pode ser futura'; } } + // Atualizar validação do campo + if (!dataValida) { + console.warn('Data inválida:', this.value, mensagemErro); + this.setCustomValidity(mensagemErro); + this.classList.add('is-invalid'); + + // Atualizar mensagem de feedback + const feedback = this.nextElementSibling; + if (feedback && feedback.classList.contains('invalid-feedback')) { + feedback.textContent = mensagemErro; + } + } else { + console.log('Data válida:', this.value); + this.setCustomValidity(''); + this.classList.remove('is-invalid'); + } + }); + + // Limpar validação ao começar a digitar + input.addEventListener('input', function() { this.setCustomValidity(''); this.classList.remove('is-invalid'); }); diff --git a/static/js/militantes.js b/static/js/militantes.js index 337b9a8..0d380d8 100644 --- a/static/js/militantes.js +++ b/static/js/militantes.js @@ -31,19 +31,20 @@ let filtroCelula = null; let currentPage = 1; let rowsPerPage = 20; let totalRows = 0; +let buscarTexto = ''; // Mapa de responsabilidades para valores numéricos const RESPONSABILIDADES_MAP = { - 'Secretário': 1, - 'Tesoureiro': 2, - 'Imprensa': 4, - 'MNS': 8, - 'MPS': 16, - 'Juventude': 32, - 'Quadro-Orientador': 64, - 'Aspirante': 128, - 'Responsável de Finanças': 256, - 'Responsável de Imprensa': 512 + 'secretario': 1, + 'tesoureiro': 2, + 'imprensa': 4, + 'mns': 8, + 'mps': 16, + 'juventude': 32, + 'quadro-orientador': 64, + 'aspirante': 128, + 'responsavel-financas': 256, + 'responsavel-imprensa': 512 }; // Mapa reverso para converter valores em nomes @@ -64,35 +65,151 @@ const RESPONSABILIDADES_REVERSE_MAP = { function validarData(data) { if (!data) return true; // Campo vazio é válido + console.log('Validando data:', data); + // Verifica o formato - if (!/^\d{2}\/\d{2}\/\d{4}$/.test(data)) return false; + if (!/^\d{2}\/\d{2}\/\d{4}$/.test(data)) { + console.warn('Data com formato inválido (deve ser DD/MM/YYYY):', 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 os valores são números válidos + if (isNaN(dia) || isNaN(mes) || isNaN(ano)) { + console.warn('Data contém valores não numéricos:', { dia, mes, ano }); + return false; + } - // Verifica se a data é válida - return dataObj.getDate() === dia && - dataObj.getMonth() === mes - 1 && - dataObj.getFullYear() === ano && - ano >= 1900 && ano <= 2100; + // Verifica se os valores estão dentro dos limites + if (mes < 1 || mes > 12) { + console.warn('Mês inválido (deve estar entre 1 e 12):', mes); + return false; + } + if (dia < 1 || dia > 31) { + console.warn('Dia inválido (deve estar entre 1 e 31):', dia); + return false; + } + if (ano < 1900 || ano > 2100) { + console.warn('Ano fora do intervalo permitido (1900-2100):', ano); + return false; + } + + // Verifica meses com 30 dias + if ([4, 6, 9, 11].includes(mes) && dia > 30) { + console.warn(`Dia inválido para o mês ${mes} (máximo 30):`, dia); + return false; + } + + // Verifica fevereiro e ano bissexto + if (mes === 2) { + const bissexto = (ano % 4 === 0 && ano % 100 !== 0) || (ano % 400 === 0); + const maxDias = bissexto ? 29 : 28; + if (dia > maxDias) { + console.warn(`Dia inválido para fevereiro ${bissexto ? '(bissexto)' : ''} (máximo ${maxDias}):`, dia); + return false; + } + } + + // Verifica se a data não é futura + const hoje = new Date(); + const dataInformada = new Date(ano, mes - 1, dia); + if (dataInformada > hoje) { + console.warn('Data futura não permitida:', data); + return false; + } + + console.log('Data válida:', data); + return true; } // Função para formatar data do formato ISO (YYYY-MM-DD) para DD/MM/YYYY function formatarData(data) { - if (!data) return ''; - const [ano, mes, dia] = data.split('-'); - return `${dia}/${mes}/${ano}`; + if (!data) { + console.log('Data vazia, retornando string vazia'); + return ''; + } + + console.log('Formatando data:', data); + + // Se já estiver no formato DD/MM/YYYY, valida e retorna + if (/^\d{2}\/\d{2}\/\d{4}$/.test(data)) { + if (validarData(data)) { + console.log('Data já está no formato correto e é válida:', data); + return data; + } else { + console.warn('Data já está no formato DD/MM/YYYY mas é inválida:', data); + return ''; + } + } + + try { + // Tenta converter do formato ISO + if (/^\d{4}-\d{2}-\d{2}$/.test(data)) { + const [ano, mes, dia] = data.split('-'); + const dataFormatada = `${dia}/${mes}/${ano}`; + + // Valida a data antes de retornar + if (validarData(dataFormatada)) { + console.log('Data ISO convertida com sucesso:', data, '->', dataFormatada); + return dataFormatada; + } else { + console.warn('Data ISO inválida após formatação:', data, '->', dataFormatada); + return ''; + } + } + + console.warn('Formato de data não reconhecido (deve ser YYYY-MM-DD ou DD/MM/YYYY):', data); + return ''; + } catch (error) { + console.error('Erro ao formatar data:', error, 'Data:', data); + return ''; + } } // Função para converter data de DD/MM/YYYY para YYYY-MM-DD function converterDataParaISO(data) { - if (!data) return ''; - if (data.includes('-')) return data; - const [dia, mes, ano] = data.split('/'); - return `${ano}-${mes}-${dia}`; + if (!data) { + console.log('Data vazia, retornando string vazia'); + return ''; + } + + console.log('Convertendo data para ISO:', data); + + // Se já estiver no formato ISO, valida e retorna + if (/^\d{4}-\d{2}-\d{2}$/.test(data)) { + const [ano, mes, dia] = data.split('-').map(Number); + if (!isNaN(ano) && !isNaN(mes) && !isNaN(dia)) { + console.log('Data já está no formato ISO:', data); + return data; + } else { + console.warn('Data no formato ISO mas com valores inválidos:', data); + return ''; + } + } + + try { + // Verifica se está no formato DD/MM/YYYY + if (/^\d{2}\/\d{2}\/\d{4}$/.test(data)) { + // Valida a data antes de converter + if (!validarData(data)) { + console.warn('Data inválida antes da conversão para ISO:', data); + return ''; + } + + const [dia, mes, ano] = data.split('/'); + const dataISO = `${ano}-${mes}-${dia}`; + console.log('Data convertida para ISO com sucesso:', data, '->', dataISO); + return dataISO; + } + + console.warn('Formato de data não reconhecido (deve ser DD/MM/YYYY):', data); + return ''; + } catch (error) { + console.error('Erro ao converter data para ISO:', error, 'Data:', data); + return ''; + } } // Função para calcular o total de páginas @@ -193,53 +310,67 @@ function updateVisibleRows() { updateCountText(); } +// Função para atualizar a contagem de militantes visíveis +function atualizarContagem() { + const allRows = document.querySelectorAll('#militantesTable tbody tr'); + const visibleRows = Array.from(allRows).filter(row => !row.hasAttribute('data-filtered-out')); + + // Atualizar o contador + document.getElementById('countMilitantes').textContent = visibleRows.length; + + // Resetar paginação + currentPage = 1; + updateVisibleRows(); + updatePagination(); +} + // Função para filtrar militantes function filtrarMilitantes() { - const searchTerm = document.getElementById('searchInput').value.toLowerCase(); - const rows = document.querySelectorAll('#militantesTable tbody tr'); - - rows.forEach(row => { - let shouldShow = true; - + console.log('Filtrando militantes...'); + console.log('Filtro atual:', filtroAtual); + console.log('Texto busca:', buscarTexto); + console.log('Filtro responsabilidade:', filtroResponsabilidade); + console.log('Filtro célula:', filtroCelula); + + const allRows = document.querySelectorAll('#militantesTable tbody tr'); + + allRows.forEach(row => { + const nome = row.querySelector('[data-nome]')?.getAttribute('data-nome')?.toLowerCase() || ''; + const cpf = row.querySelector('[data-cpf]')?.getAttribute('data-cpf')?.toLowerCase() || ''; + const email = row.querySelector('[data-email]')?.getAttribute('data-email')?.toLowerCase() || ''; + const telefone = row.querySelector('[data-telefone]')?.getAttribute('data-telefone')?.toLowerCase() || ''; + // Filtro de texto - const textContent = row.textContent.toLowerCase(); - if (!textContent.includes(searchTerm)) { - shouldShow = false; - } - + const matchesSearch = buscarTexto === '' || + nome.includes(buscarTexto) || + cpf.includes(buscarTexto) || + email.includes(buscarTexto) || + telefone.includes(buscarTexto); + // Filtro de responsabilidades + let matchesResponsabilidade = true; if (filtroResponsabilidade) { - const responsabilidadeMap = { - 'responsavel-financas': Militante.RESPONSAVEL_FINANCAS, - 'responsavel-imprensa': Militante.RESPONSAVEL_IMPRENSA, - 'quadro-orientador': Militante.QUADRO_ORIENTADOR, - 'secretario': Militante.SECRETARIO, - 'tesoureiro': Militante.TESOUREIRO, - 'imprensa': Militante.IMPRENSA, - 'mns': Militante.MNS, - 'mps': Militante.MPS, - 'juventude': Militante.JUVENTUDE, - 'aspirante': Militante.ASPIRANTE - }; - - const valorResponsabilidade = responsabilidadeMap[filtroResponsabilidade]; - const responsabilidades = parseInt(row.getAttribute('data-responsabilidades') || '0'); - - if ((responsabilidades & valorResponsabilidade) === 0) { - shouldShow = false; + const valorResponsabilidade = RESPONSABILIDADES_MAP[filtroResponsabilidade]; + console.log('Valor da responsabilidade:', valorResponsabilidade); + if (valorResponsabilidade !== undefined) { + const responsabilidades = parseInt(row.getAttribute('data-responsabilidades') || '0'); + console.log('Responsabilidades do militante:', responsabilidades); + matchesResponsabilidade = (responsabilidades & valorResponsabilidade) !== 0; + console.log('Matches responsabilidade:', matchesResponsabilidade); } } - + // Filtro de célula + let matchesCelula = true; if (filtroCelula) { - const celula = row.querySelector('[data-celula-id]')?.getAttribute('data-celula-id'); - if (celula !== filtroCelula) { - shouldShow = false; - } + const celulaId = row.getAttribute('data-celula-id'); + console.log('Célula do militante:', celulaId, 'Filtro:', filtroCelula); + matchesCelula = celulaId === filtroCelula; + console.log('Matches célula:', matchesCelula); } - - // Marcar linha como filtrada ou não - if (shouldShow) { + + // Aplicar filtros + if (matchesSearch && matchesResponsabilidade && matchesCelula) { row.removeAttribute('data-filtered-out'); row.style.display = ''; } else { @@ -247,13 +378,21 @@ function filtrarMilitantes() { row.style.display = 'none'; } }); - - // Resetar para a primeira página e atualizar paginação - currentPage = 1; - updateVisibleRows(); - updatePagination(); + + // Atualizar contagem e paginação + atualizarContagem(); } +// Listener para o campo de busca com debounce +let timeoutId; +document.getElementById('searchInput').addEventListener('input', function(e) { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + buscarTexto = this.value.toLowerCase(); + filtrarMilitantes(); + }, 300); +}); + // Função para configurar um campo de data function configurarCampoData(campo) { if (!campo) return; @@ -280,120 +419,193 @@ function configurarCampoData(campo) { }); } -// Função para carregar os dados do militante no modal de edição +// Função para carregar dados do militante async function carregarDadosMilitante(militanteId) { try { console.log('Carregando dados do militante:', militanteId); - const response = await fetch(`/militantes/dados/${militanteId}`); - const data = await response.json(); - if (data.status === 'error') { - throw new Error(data.message); - } - - console.log('Dados recebidos:', data); - - // Mapear campos básicos - const campos = [ - 'nome', 'cpf', 'titulo_eleitoral', 'data_nascimento', 'data_entrada_oci', - 'data_efetivacao_oci', 'telefone1', 'telefone2', 'profissao', - 'regime_trabalho', 'empresa', 'contratante', 'instituicao_ensino', - 'tipo_instituicao', 'sindicato', 'cargo_sindical', 'central_sindical', - 'dirigente_sindical' - ]; - - campos.forEach(campo => { - const elemento = document.getElementById(`edit_${campo}`); - if (elemento) { - if (elemento.type === 'checkbox') { - elemento.checked = data[campo]; - } else { - elemento.value = data[campo] || ''; - } + const response = await fetch(`/militantes/dados/${militanteId}`, { + method: 'GET', + headers: { + 'X-Requested-With': 'XMLHttpRequest' } }); - // Preencher o email (primeiro email da lista) - const emailElement = document.getElementById('edit_email'); - if (emailElement) { - if (data.emails && data.emails.length > 0) { - emailElement.value = data.emails[0]; // O email já vem como string - } else { - emailElement.value = ''; + if (!response.ok) { + throw new Error(`Erro ao carregar dados do militante: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + console.log('Dados recebidos do servidor:', data); + + if (data.status === 'error') { + throw new Error(data.message || 'Erro ao carregar dados do militante'); + } + + // Preencher campos do formulário com os dados do banco + document.getElementById('edit_militante_id').value = militanteId; + document.getElementById('edit_nome').value = data.nome || ''; + document.getElementById('edit_cpf').value = data.cpf || ''; + document.getElementById('edit_titulo_eleitoral').value = data.titulo_eleitoral || ''; + + // Função auxiliar para tratar campo de data + function tratarCampoData(campo, valor, nomeCampo) { + if (!campo) { + console.warn(`Campo ${nomeCampo} não encontrado no DOM`); + return; + } + + // Limpar campo e remover validações + campo.value = ''; + campo.classList.remove('is-invalid'); + const feedback = campo.nextElementSibling; + if (feedback && feedback.classList.contains('invalid-feedback')) { + feedback.remove(); + } + + if (!valor) { + console.log(`${nomeCampo} não informada`); + return; + } + + try { + console.log(`Formatando ${nomeCampo}:`, valor); + const dataFormatada = formatarData(valor); + + if (validarData(dataFormatada)) { + campo.value = dataFormatada; + console.log(`${nomeCampo} formatada com sucesso:`, dataFormatada); + } else { + console.warn(`${nomeCampo} inválida após formatação:`, valor); + campo.classList.add('is-invalid'); + const div = document.createElement('div'); + div.className = 'invalid-feedback'; + div.textContent = 'Data inválida'; + campo.parentNode.insertBefore(div, campo.nextSibling); + } + } catch (error) { + console.error(`Erro ao formatar ${nomeCampo}:`, error); + campo.classList.add('is-invalid'); + const div = document.createElement('div'); + div.className = 'invalid-feedback'; + div.textContent = 'Erro ao formatar data'; + campo.parentNode.insertBefore(div, campo.nextSibling); } } - // Mapear campos de endereço - if (data.endereco) { - const camposEndereco = ['cep', 'estado', 'cidade', 'bairro', 'rua', 'numero', 'complemento']; - camposEndereco.forEach(campo => { - const elemento = document.getElementById(`edit_${campo}`); - if (elemento) { - elemento.value = data.endereco[campo] || ''; + // Tratar campos de data + tratarCampoData( + document.getElementById('edit_data_nascimento'), + data.data_nascimento, + 'Data de Nascimento' + ); + + tratarCampoData( + document.getElementById('edit_data_entrada_oci'), + data.data_entrada_oci, + 'Data de Entrada na OCI' + ); + + tratarCampoData( + document.getElementById('edit_data_efetivacao_oci'), + data.data_efetivacao_oci, + 'Data de Efetivação na OCI' + ); + + // Preencher outros campos + document.getElementById('edit_email').value = data.emails?.[0] || ''; + document.getElementById('edit_telefone1').value = data.telefone1 || ''; + + // Setar célula + const selectCelula = document.getElementById('edit_celula'); + if (selectCelula) { + selectCelula.value = data.celula_id || ''; + console.log('Célula definida:', data.celula_id); + } else { + console.warn('Seletor de célula não encontrado'); + } + + // Setar responsabilidades + const responsabilidadesValue = data.responsabilidades_valor || 0; + document.getElementById('responsabilidades_values').value = responsabilidadesValue; + console.log('Responsabilidades definidas:', responsabilidadesValue); + + // Atualizar badges de responsabilidades + const badges = document.querySelectorAll('.badge-clickable'); + badges.forEach(badge => { + const valor = parseInt(badge.getAttribute('data-valor')); + if (responsabilidadesValue & valor) { + badge.classList.add('active'); + const originalClass = badge.getAttribute('data-original-class'); + if (originalClass) { + badge.className = `badge badge-clickable active ${originalClass}`; } - }); - } - - // Mapear estado - const selectEstado = document.getElementById('edit_estado'); - if (selectEstado && data.estado) { - selectEstado.value = data.estado; - } - - // Mapear célula - const selectCelula = document.getElementById('edit_celula_id'); - if (selectCelula && data.celula_id) { - selectCelula.value = data.celula_id; - } - - // Processar responsabilidades - if (data.responsabilidades_valor !== undefined) { - console.log('Valor das responsabilidades:', data.responsabilidades_valor); - - // Resetar todas as badges para seu estado inicial - document.querySelectorAll('.badge-clickable').forEach(badge => { + console.log('Badge ativada:', badge.getAttribute('title')); + } else { badge.classList.remove('active'); const originalClass = badge.getAttribute('data-original-class'); if (originalClass) { badge.className = `badge badge-clickable ${originalClass}`; } - }); - - // Ativar as badges correspondentes - document.querySelectorAll('.badge-clickable').forEach(badge => { - const valor = parseInt(badge.getAttribute('data-value')); - if (!isNaN(valor) && (data.responsabilidades_valor & valor)) { - badge.classList.add('active'); - } - }); - - // Atualizar o campo hidden com o valor total - document.getElementById('responsabilidades_values').value = data.responsabilidades_valor; + } + }); + + // Setar campos de sindicato + ['sindicato', 'cargo_sindical', 'central_sindical'].forEach(campo => { + const elemento = document.getElementById(campo); + if (elemento) { + elemento.value = data[campo] || ''; + console.log(`Campo ${campo} definido:`, data[campo]); + } + }); + + // Setar checkbox de dirigente sindical + const checkDirigente = document.getElementById('dirigente_sindical'); + if (checkDirigente) { + checkDirigente.checked = data.dirigente_sindical || false; + console.log('Dirigente sindical:', data.dirigente_sindical); } + // Atualizar título do modal + const modalTitle = document.querySelector('#modalEditarMilitante .modal-title'); + if (modalTitle) { + modalTitle.innerHTML = `Editar ${data.nome}`; + console.log('Título do modal atualizado'); + } + + console.log('Dados carregados com sucesso'); + } catch (error) { console.error('Erro ao carregar dados:', error); - alert('Erro ao carregar dados do militante'); + mostrarAlerta('danger', `Erro ao carregar dados do militante: ${error.message}`); } } // Função para mostrar alertas -function mostrarAlerta(mensagem, tipo) { +function mostrarAlerta(tipo, mensagem) { + // Remover alertas existentes + document.querySelectorAll('.alert').forEach(alert => alert.remove()); + const alertDiv = document.createElement('div'); alertDiv.className = `alert alert-${tipo} alert-dismissible fade show`; alertDiv.role = 'alert'; + alertDiv.style.marginBottom = '1rem'; alertDiv.innerHTML = ` ${mensagem} - + `; - const container = document.querySelector('.container'); - container.insertBefore(alertDiv, container.firstChild); - - // Remover o alerta após 5 segundos - setTimeout(() => { - alertDiv.remove(); - }, 5000); + const container = document.querySelector('.container-fluid'); + if (container) { + container.insertBefore(alertDiv, container.firstChild); + + // Remover o alerta após 5 segundos + setTimeout(() => { + if (alertDiv && alertDiv.parentNode) { + alertDiv.remove(); + } + }, 5000); + } } // Configurar o token CSRF para todas as requisições AJAX @@ -420,47 +632,37 @@ document.addEventListener('DOMContentLoaded', function() { }); } - // Configurar pesquisa - const searchInput = document.getElementById('searchInput'); - if (searchInput) { - let timeoutId; - searchInput.addEventListener('input', function() { - clearTimeout(timeoutId); - timeoutId = setTimeout(filtrarMilitantes, 300); - }); - } - // Configurar filtros document.querySelectorAll('.dropdown-item[data-filter]').forEach(item => { item.addEventListener('click', function(e) { e.preventDefault(); const filter = this.getAttribute('data-filter'); - const celula = this.getAttribute('data-celula'); + const dropdownButton = document.querySelector('.dropdown-toggle'); + + console.log('Filtro selecionado:', filter); - // Resetar filtros anteriores if (filter === 'todos') { - filtroAtual = 'todos'; filtroResponsabilidade = null; filtroCelula = null; - } else if (['responsavel-financas', 'responsavel-imprensa', 'quadro-orientador'].includes(filter)) { - filtroAtual = 'todos'; - filtroResponsabilidade = filter === 'responsavel-financas' ? 'Responsável de Finanças' : - filter === 'responsavel-imprensa' ? 'Responsável de Imprensa' : - 'Quadro-Orientador'; - filtroCelula = null; } else if (filter === 'celula') { - filtroAtual = 'todos'; filtroResponsabilidade = null; - filtroCelula = celula; + filtroCelula = this.getAttribute('data-celula'); + console.log('Filtro por célula:', filtroCelula); + } else { + filtroResponsabilidade = filter; + filtroCelula = null; + console.log('Filtro por responsabilidade:', filtroResponsabilidade); } - filtrarMilitantes(); + // Atualizar filtro atual + filtroAtual = filter; - // Atualizar texto do botão de filtro - const filterText = this.textContent; - const dropdownButton = document.querySelector('.dropdown-toggle'); - dropdownButton.innerHTML = `${filterText}`; + // Atualizar texto do botão + dropdownButton.innerHTML = `${this.textContent}`; + + // Aplicar filtros + filtrarMilitantes(); }); }); @@ -469,20 +671,56 @@ document.addEventListener('DOMContentLoaded', function() { // Configurar máscara para campos de data $('.date-mask').mask('00/00/0000', { placeholder: 'DD/MM/AAAA', - clearIfNotMatch: true + clearIfNotMatch: true, + translation: { + 'placeholder': "DD/MM/AAAA" + } }); // Validar campos de data quando perderem o foco $('.date-mask').on('blur', function() { const valor = $(this).val(); - if (valor && !validarData(valor)) { + if (valor) { + if (!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(); + } + } else { + // Se o campo estiver vazio, remover validação + $(this).removeClass('is-invalid'); + $(this).next('.invalid-feedback').remove(); + } + }); + + // Impedir entrada de caracteres inválidos nos campos de data + $('.date-mask').on('keypress', function(e) { + const char = String.fromCharCode(e.which); + if (!/[\d\/]/.test(char)) { + e.preventDefault(); + return false; + } + + const valor = $(this).val(); + if (valor.length === 10 && char !== '\b') { + e.preventDefault(); + return false; + } + }); + + // Limpar campos de data quando o valor for inválido + $('.date-mask').on('input', function() { + const valor = $(this).val(); + if (valor && valor.length === 10 && !validarData(valor)) { + $(this).val(''); $(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(); } }); @@ -498,7 +736,7 @@ document.addEventListener('DOMContentLoaded', function() { if (!button) { console.error('Botão não encontrado'); - alert('Erro ao abrir modal: botão não encontrado'); + mostrarAlerta('danger', 'Erro ao abrir modal: botão não encontrado'); return; } @@ -509,253 +747,53 @@ document.addEventListener('DOMContentLoaded', function() { if (!militanteId) { console.error('ID do militante não encontrado no botão'); - alert('Erro ao abrir modal: ID do militante não encontrado'); + mostrarAlerta('danger', 'Erro ao abrir modal: ID do militante não encontrado'); return; } - // Definir o ID do militante no campo hidden - const idField = document.getElementById('edit_militante_id'); - if (!idField) { - console.error('Campo hidden para ID do militante não encontrado'); - alert('Erro ao abrir modal: campo para ID do militante não encontrado'); - return; - } - - idField.value = militanteId; - console.log('ID do militante definido:', idField.value); - // Atualizar título do modal const modalTitle = this.querySelector('.modal-title'); if (modalTitle) { - modalTitle.textContent = `Editar ${militanteNome}`; + modalTitle.innerHTML = `Editar ${militanteNome}`; } try { // Carregar dados do militante - const response = await fetch(`/militantes/dados/${militanteId}`); - if (!response.ok) { - throw new Error(`Erro HTTP: ${response.status}`); - } - - const data = await response.json(); - console.log('Dados recebidos:', data); - - if (data.status === 'error') { - throw new Error(data.message || 'Erro ao carregar dados do militante'); - } - - // Mapear campos básicos - const campos = [ - 'nome', 'cpf', 'titulo_eleitoral', 'data_nascimento', 'data_entrada_oci', - 'data_efetivacao_oci', 'telefone1', 'telefone2', 'profissao', - 'regime_trabalho', 'empresa', 'contratante', 'instituicao_ensino', - 'tipo_instituicao', 'sindicato', 'cargo_sindical', 'central_sindical', - 'dirigente_sindical' - ]; - - campos.forEach(campo => { - const elemento = document.getElementById(`edit_${campo}`); - if (elemento) { - if (elemento.type === 'checkbox') { - elemento.checked = data[campo]; - } else { - elemento.value = data[campo] || ''; - } - } - }); - - // Preencher o email (primeiro email da lista) - const emailElement = document.getElementById('edit_email'); - if (emailElement) { - if (data.emails && data.emails.length > 0) { - emailElement.value = data.emails[0]; // O email já vem como string - } else { - emailElement.value = ''; - } - } - - // Mapear campos de endereço - if (data.endereco) { - const camposEndereco = ['cep', 'estado', 'cidade', 'bairro', 'rua', 'numero', 'complemento']; - camposEndereco.forEach(campo => { - const elemento = document.getElementById(`edit_${campo}`); - if (elemento) { - elemento.value = data.endereco[campo] || ''; - } - }); - } - - // Mapear estado - const selectEstado = document.getElementById('edit_estado'); - if (selectEstado && data.estado) { - selectEstado.value = data.estado; - } - - // Mapear célula - const selectCelula = document.getElementById('edit_celula_id'); - if (selectCelula && data.celula_id) { - selectCelula.value = data.celula_id; - } - - // Processar responsabilidades - if (data.responsabilidades_valor !== undefined) { - console.log('Valor das responsabilidades:', data.responsabilidades_valor); - - // Resetar todas as badges para seu estado inicial - document.querySelectorAll('.badge-clickable').forEach(badge => { - badge.classList.remove('active'); - const originalClass = badge.getAttribute('data-original-class'); - if (originalClass) { - badge.className = `badge badge-clickable ${originalClass}`; - } - }); - - // Ativar as badges correspondentes - document.querySelectorAll('.badge-clickable').forEach(badge => { - const valor = parseInt(badge.getAttribute('data-value')); - if (!isNaN(valor) && (data.responsabilidades_valor & valor)) { - badge.classList.add('active'); - } - }); - - // Atualizar o campo hidden com o valor total - document.getElementById('responsabilidades_values').value = data.responsabilidades_valor; - } - + await carregarDadosMilitante(militanteId); } catch (error) { console.error('Erro ao carregar dados:', error); - alert('Erro ao carregar dados do militante'); + mostrarAlerta('danger', 'Erro ao carregar dados do militante'); } }); - // Configurar clique nas badges - document.querySelectorAll('.badge-clickable').forEach(badge => { - badge.addEventListener('click', function() { - // Toggle classe active - this.classList.toggle('active'); - - // Atualizar classes visuais - if (this.classList.contains('active')) { - const originalClass = this.getAttribute('data-original-class'); - this.className = `badge badge-clickable ${originalClass} active`; - } else { - const originalClass = this.getAttribute('data-original-class'); - this.className = `badge badge-clickable ${originalClass}`; - } - - // Atualizar o valor total das responsabilidades - let valorTotal = 0; - document.querySelectorAll('.badge-clickable.active').forEach(activeBadge => { - const valor = parseInt(activeBadge.getAttribute('data-value')); - if (!isNaN(valor)) { - valorTotal |= valor; - } - }); - - // Atualizar o campo hidden - const responsabilidadesInput = document.getElementById('responsabilidades_values'); - if (responsabilidadesInput) { - responsabilidadesInput.value = valorTotal; - console.log('Valor total das responsabilidades atualizado:', valorTotal); + // Limpar dados quando o modal for fechado + modalEditarMilitante.addEventListener('hidden.bs.modal', function() { + // Limpar formulário + const form = this.querySelector('form'); + if (form) { + form.reset(); + } + + // Limpar campos hidden + document.getElementById('edit_militante_id').value = ''; + document.getElementById('responsabilidades_values').value = '0'; + + // Resetar badges + this.querySelectorAll('.badge-clickable').forEach(badge => { + badge.classList.remove('active'); + const originalClass = badge.getAttribute('data-original-class'); + if (originalClass) { + badge.className = `badge badge-clickable ${originalClass}`; } }); - }); - - // Configurar formulário de edição - const formEditarMilitante = document.getElementById('formEditarMilitante'); - if (formEditarMilitante) { - formEditarMilitante.addEventListener('submit', async function(event) { - event.preventDefault(); - console.log('Formulário de edição enviado'); - - // Validar todos os campos antes do envio - const form = this; - form.classList.add('was-validated'); - - // Verificar se o formulário é válido - if (!form.checkValidity()) { - event.stopPropagation(); - - // Encontrar o primeiro campo inválido - const invalidField = form.querySelector(':invalid'); - if (invalidField) { - // Encontrar a aba que contém o campo inválido - const tabPane = invalidField.closest('.tab-pane'); - if (tabPane) { - // Ativar a aba - const tabId = tabPane.id; - const tab = document.querySelector(`button[data-bs-target="#${tabId}"]`); - if (tab) { - const bsTab = new bootstrap.Tab(tab); - bsTab.show(); - - // Focar no campo inválido após a aba ser exibida - setTimeout(() => { - invalidField.focus(); - }, 200); - } - } - } - return; - } - - // Se chegou aqui, o formulário é válido - // Obter ID do militante - const militanteId = document.getElementById('edit_militante_id').value; - - if (!militanteId) { - console.error('ID do militante não encontrado'); - alert('Erro ao salvar: ID do militante não encontrado'); - return; - } - - // Criar FormData com todos os campos - const formData = new FormData(form); - - // Garantir que o valor das responsabilidades seja enviado - const responsabilidadesValue = document.getElementById('responsabilidades_values').value; - formData.set('responsabilidades_valor', responsabilidadesValue); - - try { - const url = `/militantes/editar/${militanteId}`; - const csrfToken = document.querySelector('input[name="csrf_token"]').value; - - const response = await fetch(url, { - method: 'POST', - headers: { - 'X-Requested-With': 'XMLHttpRequest', - 'X-CSRF-Token': csrfToken - }, - body: formData - }); - - if (!response.ok) { - throw new Error(`Erro HTTP: ${response.status}`); - } - - const data = await response.json(); - - if (data.status === 'success') { - const modal = bootstrap.Modal.getInstance(document.getElementById('modalEditarMilitante')); - modal.hide(); - mostrarAlerta('Militante atualizado com sucesso!', 'success'); - setTimeout(() => { - location.reload(); - }, 1000); - } else { - throw new Error(data.message || 'Erro desconhecido ao salvar militante'); - } - } catch (error) { - console.error('Erro ao salvar:', error); - mostrarAlerta(`Erro ao salvar militante: ${error.message}`, 'danger'); - } + + // Limpar feedback de validação + this.querySelectorAll('.is-invalid').forEach(campo => { + campo.classList.remove('is-invalid'); + }); + this.querySelectorAll('.invalid-feedback').forEach(feedback => { + feedback.remove(); }); - } - - // Limpar alertas quando o modal for fechado - modalEditarMilitante.addEventListener('hidden.bs.modal', function () { - const alerts = this.querySelectorAll('.alert'); - alerts.forEach(alert => alert.remove()); }); } else { console.error('Modal de edição não encontrado!'); @@ -1082,6 +1120,15 @@ async function salvarAlteracoesMilitante(militanteId) { const form = document.getElementById('formEditarMilitante'); const formData = new FormData(form); + // Garantir que as datas estão no formato correto (YYYY-MM-DD) + const dataNascimento = formData.get('data_nascimento'); + const dataEntradaOci = formData.get('data_entrada_oci'); + const dataEfetivacaoOci = formData.get('data_efetivacao_oci'); + + if (dataNascimento) formData.set('data_nascimento', converterDataParaISO(dataNascimento)); + if (dataEntradaOci) formData.set('data_entrada_oci', converterDataParaISO(dataEntradaOci)); + if (dataEfetivacaoOci) formData.set('data_efetivacao_oci', converterDataParaISO(dataEfetivacaoOci)); + // Adicionar responsabilidades let responsabilidadesValor = 0; form.querySelectorAll('input[name="responsabilidades"]:checked').forEach(checkbox => { @@ -1093,22 +1140,322 @@ async function salvarAlteracoesMilitante(militanteId) { method: 'POST', body: formData, headers: { - 'X-CSRFToken': getCsrfToken() + 'X-CSRFToken': getCsrfToken(), + 'X-Requested-With': 'XMLHttpRequest' } }); const data = await response.json(); + console.log('Resposta completa do servidor:', data); if (data.status === 'success') { - mostrarAlerta(data.message, 'success'); - setTimeout(() => { - window.location.reload(); - }, 1500); + mostrarAlerta('success', data.message); + // Atualizar a linha na tabela + console.log('Dados detalhados para atualizarLinhaMilitante:', JSON.stringify(data, null, 2)); + atualizarLinhaMilitante(militanteId, data); + console.log('Linha da tabela atualizada'); + + // Fechar o modal + const modal = bootstrap.Modal.getInstance(document.getElementById('modalEditarMilitante')); + if (modal) { + modal.hide(); + } } else { - mostrarAlerta(data.message || 'Erro ao salvar alterações', 'danger'); + mostrarAlerta('danger', data.message || 'Erro ao salvar alterações'); } } catch (error) { console.error('Erro ao salvar alterações:', error); - mostrarAlerta('Erro ao salvar alterações. Por favor, tente novamente.', 'danger'); + mostrarAlerta('danger', 'Erro ao salvar alterações. Por favor, tente novamente.'); } -} \ No newline at end of file +} + +// Função para atualizar linha do militante na tabela +function atualizarLinhaMilitante(militanteId, dados) { + console.log('Atualizando linha do militante:', militanteId, dados); + const row = document.querySelector(`tr[data-militante="${militanteId}"]`); + if (row) { + // Atualizar dados básicos e seus atributos data-* + const tdNome = row.querySelector('[data-nome]'); + if (tdNome) { + console.log('Atualizando nome:', dados.data.nome); + tdNome.textContent = dados.data.nome; + tdNome.setAttribute('data-nome', dados.data.nome); + } + + const tdCpf = row.querySelector('[data-cpf]'); + if (tdCpf) { + console.log('Atualizando CPF:', dados.data.cpf); + tdCpf.textContent = dados.data.cpf; + tdCpf.setAttribute('data-cpf', dados.data.cpf); + } + + const tdEmail = row.querySelector('[data-email]'); + if (tdEmail) { + const email = dados.data.emails && dados.data.emails.length > 0 ? dados.data.emails[0] : ''; + console.log('Atualizando email:', email); + tdEmail.textContent = email; + tdEmail.setAttribute('data-email', email); + } + + const tdTelefone = row.querySelector('[data-telefone]'); + if (tdTelefone) { + const telefone = dados.data.telefone1 || ''; + console.log('Atualizando telefone:', telefone); + tdTelefone.textContent = telefone; + tdTelefone.setAttribute('data-telefone', telefone); + } + + // Atualizar atributos para filtros + console.log('Atualizando célula ID:', dados.data.celula_id); + row.setAttribute('data-celula-id', dados.data.celula_id || ''); + console.log('Atualizando responsabilidades:', dados.data.responsabilidades_valor); + row.setAttribute('data-responsabilidades', dados.data.responsabilidades_valor || '0'); + + // Atualizar badges de responsabilidades + const tdResponsabilidades = row.querySelector('td:nth-child(5)'); + if (tdResponsabilidades) { + console.log('Atualizando badges de responsabilidades'); + tdResponsabilidades.innerHTML = gerarBadgesResponsabilidades(dados.data.responsabilidades_valor); + } + + // Remover qualquer estado de filtro da linha + row.removeAttribute('data-filtered-out'); + row.style.display = ''; + + // Verificar se a linha deve estar visível com os filtros atuais + const nome = row.querySelector('[data-nome]')?.getAttribute('data-nome')?.toLowerCase() || ''; + const cpf = row.querySelector('[data-cpf]')?.getAttribute('data-cpf')?.toLowerCase() || ''; + const email = row.querySelector('[data-email]')?.getAttribute('data-email')?.toLowerCase() || ''; + const telefone = row.querySelector('[data-telefone]')?.getAttribute('data-telefone')?.toLowerCase() || ''; + + // Filtro de texto + const matchesSearch = buscarTexto === '' || + nome.includes(buscarTexto) || + cpf.includes(buscarTexto) || + email.includes(buscarTexto) || + telefone.includes(buscarTexto); + + // Filtro de responsabilidades + let matchesResponsabilidade = true; + if (filtroResponsabilidade) { + const valorResponsabilidade = RESPONSABILIDADES_MAP[filtroResponsabilidade]; + if (valorResponsabilidade !== undefined) { + const responsabilidades = parseInt(row.getAttribute('data-responsabilidades') || '0'); + matchesResponsabilidade = (responsabilidades & valorResponsabilidade) !== 0; + } + } + + // Filtro de célula + let matchesCelula = true; + if (filtroCelula) { + const celulaId = row.getAttribute('data-celula-id'); + matchesCelula = celulaId === filtroCelula; + } + + // Aplicar filtros mantendo a linha visível se corresponder aos critérios + if (matchesSearch && matchesResponsabilidade && matchesCelula) { + row.removeAttribute('data-filtered-out'); + row.style.display = ''; + } else { + row.setAttribute('data-filtered-out', ''); + row.style.display = 'none'; + } + + // Atualizar a paginação sem recarregar a página + updateVisibleRows(); + updatePagination(); + } +} + +// Função para gerar HTML das badges de responsabilidades +function gerarBadgesResponsabilidades(valor) { + const badges = []; + if (valor & RESPONSABILIDADES_MAP['responsavel-financas']) badges.push('RFI'); + if (valor & RESPONSABILIDADES_MAP['responsavel-imprensa']) badges.push('RIM'); + if (valor & RESPONSABILIDADES_MAP['quadro-orientador']) badges.push('QOR'); + if (valor & RESPONSABILIDADES_MAP['secretario']) badges.push('SEC'); + if (valor & RESPONSABILIDADES_MAP['tesoureiro']) badges.push('TES'); + if (valor & RESPONSABILIDADES_MAP['imprensa']) badges.push('IMP'); + if (valor & RESPONSABILIDADES_MAP['mns']) badges.push('MNS'); + if (valor & RESPONSABILIDADES_MAP['mps']) badges.push('MPS'); + if (valor & RESPONSABILIDADES_MAP['juventude']) badges.push('JUV'); + if (valor & RESPONSABILIDADES_MAP['aspirante']) badges.push('ASP'); + return badges.join(' '); +} + +// Handler para o formulário de edição +document.getElementById('formEditarMilitante').addEventListener('submit', async function(e) { + e.preventDefault(); + console.log('Iniciando envio do formulário de edição...'); + + // Função auxiliar para validar e converter data + function validarEConverterData(campo, nomeCampo) { + const valor = campo.value; + console.log(`Validando ${nomeCampo}:`, valor); + + if (!valor) { + console.log(`${nomeCampo} não informada`); + return true; + } + + if (!validarData(valor)) { + console.warn(`${nomeCampo} inválida:`, valor); + campo.classList.add('is-invalid'); + if (!campo.nextElementSibling?.classList.contains('invalid-feedback')) { + const div = document.createElement('div'); + div.className = 'invalid-feedback'; + div.textContent = 'Data inválida'; + campo.parentNode.insertBefore(div, campo.nextSibling); + } + return false; + } + + const dataISO = converterDataParaISO(valor); + if (!dataISO) { + console.warn(`Erro ao converter ${nomeCampo} para ISO:`, valor); + campo.classList.add('is-invalid'); + if (!campo.nextElementSibling?.classList.contains('invalid-feedback')) { + const div = document.createElement('div'); + div.className = 'invalid-feedback'; + div.textContent = 'Erro ao converter data'; + campo.parentNode.insertBefore(div, campo.nextSibling); + } + return false; + } + + console.log(`${nomeCampo} validada e convertida:`, valor, '->', dataISO); + return true; + } + + // Validar todas as datas + const camposData = { + 'Data de Nascimento': document.getElementById('edit_data_nascimento'), + 'Data de Entrada na OCI': document.getElementById('edit_data_entrada_oci'), + 'Data de Efetivação na OCI': document.getElementById('edit_data_efetivacao_oci') + }; + + let datasValidas = true; + for (const [nome, campo] of Object.entries(camposData)) { + if (campo && !validarEConverterData(campo, nome)) { + datasValidas = false; + } + } + + if (!datasValidas) { + console.warn('Formulário contém datas inválidas'); + mostrarAlerta('danger', 'Por favor, corrija as datas inválidas antes de salvar.'); + return; + } + + // Validar sequência lógica das datas + const dataNascimento = camposData['Data de Nascimento']?.value; + const dataEntrada = camposData['Data de Entrada na OCI']?.value; + const dataEfetivacao = camposData['Data de Efetivação na OCI']?.value; + + if (dataNascimento && dataEntrada) { + const nascimento = new Date(converterDataParaISO(dataNascimento)); + const entrada = new Date(converterDataParaISO(dataEntrada)); + + if (entrada < nascimento) { + console.warn('Data de entrada anterior à data de nascimento'); + mostrarAlerta('danger', 'A data de entrada na OCI não pode ser anterior à data de nascimento.'); + return; + } + } + + if (dataEntrada && dataEfetivacao) { + const entrada = new Date(converterDataParaISO(dataEntrada)); + const efetivacao = new Date(converterDataParaISO(dataEfetivacao)); + + if (efetivacao < entrada) { + console.warn('Data de efetivação anterior à data de entrada'); + mostrarAlerta('danger', 'A data de efetivação não pode ser anterior à data de entrada na OCI.'); + return; + } + } + + const militanteId = document.getElementById('edit_militante_id').value; + console.log('ID do militante:', militanteId); + + const formData = new FormData(this); + + try { + // Converter datas para formato ISO + for (const [nome, campo] of Object.entries(camposData)) { + if (campo?.value) { + const dataISO = converterDataParaISO(campo.value); + formData.set(campo.name, dataISO); + console.log(`${nome} convertida para envio:`, campo.value, '->', dataISO); + } + } + + console.log('Enviando dados para o servidor...'); + const response = await fetch(`/militantes/editar/${militanteId}`, { + method: 'POST', + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') + }, + body: formData + }); + + if (!response.ok) { + throw new Error(`Erro ${response.status}: ${response.statusText}`); + } + + const data = await response.json(); + console.log('Resposta do servidor:', data); + + if (data.status === 'success') { + // Atualizar linha na tabela + console.log('Dados detalhados para atualizarLinhaMilitante:', JSON.stringify(data, null, 2)); + atualizarLinhaMilitante(militanteId, data); + console.log('Linha da tabela atualizada'); + + // Mostrar mensagem de sucesso + mostrarAlerta('success', data.message); + + // Fechar modal + const modal = bootstrap.Modal.getInstance(document.getElementById('modalEditarMilitante')); + if (modal) { + modal.hide(); + console.log('Modal fechado'); + } + } else { + console.warn('Erro retornado pelo servidor:', data.message); + mostrarAlerta('danger', data.message || 'Erro ao salvar alterações'); + } + } catch (error) { + console.error('Erro ao salvar alterações:', error); + mostrarAlerta('danger', `Erro ao salvar alterações: ${error.message}`); + } +}); + +// Handler para clique nas badges de responsabilidade +document.querySelectorAll('.badge-clickable').forEach(badge => { + badge.addEventListener('click', function() { + const valor = parseInt(this.getAttribute('data-valor')); + const responsabilidadesInput = document.getElementById('responsabilidades_values'); + let valorAtual = parseInt(responsabilidadesInput.value) || 0; + + if (this.classList.contains('active')) { + // Remover responsabilidade + valorAtual &= ~valor; // usando operador bitwise NOT e AND + this.classList.remove('active'); + const originalClass = this.getAttribute('data-original-class'); + if (originalClass) { + this.className = `badge badge-clickable ${originalClass}`; + } + } else { + // Adicionar responsabilidade + valorAtual |= valor; // usando operador bitwise OR + this.classList.add('active'); + const originalClass = this.getAttribute('data-original-class'); + if (originalClass) { + this.className = `badge badge-clickable active ${originalClass}`; + } + } + + responsabilidadesInput.value = valorAtual; + }); +}); \ No newline at end of file diff --git a/static/js/pagamentos.js b/static/js/pagamentos.js index 40a876c..75e4300 100644 --- a/static/js/pagamentos.js +++ b/static/js/pagamentos.js @@ -6,10 +6,30 @@ document.addEventListener('DOMContentLoaded', function() { language: { url: '//cdn.datatables.net/plug-ins/1.13.7/i18n/pt-BR.json' }, - order: [[3, 'desc']], // Ordenar por data de pagamento (decrescente) columnDefs: [ - { targets: -1, orderable: false } // Desabilitar ordenação na coluna de ações - ] + { + targets: 3, // Coluna de data + type: 'date-br', + render: function(data, type, row) { + if (type === 'sort') { + return data.split('/').reverse().join(''); + } + return data; + } + }, + { + targets: 2, // Coluna de valor + type: 'numeric', + render: function(data, type, row) { + if (type === 'sort') { + return parseFloat(data.replace('R$ ', '').replace(',', '.')); + } + return data; + } + }, + { targets: -1, orderable: false } // Coluna de ações + ], + order: [[3, 'desc']] // Ordenar por data decrescente por padrão }); // Configuração do modal de edição @@ -253,4 +273,44 @@ document.addEventListener('DOMContentLoaded', function() { } }); } + + // Funções de validação e formatação de datas + function validarData(data) { + if (!data) return false; + + const dataObj = new Date(data); + if (isNaN(dataObj.getTime())) return false; + + const hoje = new Date(); + hoje.setHours(0, 0, 0, 0); + + return dataObj <= hoje; + } + + function formatarData(data) { + if (!data) return ''; + + const dataObj = new Date(data); + if (isNaN(dataObj.getTime())) return ''; + + return dataObj.toLocaleDateString('pt-BR'); + } + + // Configurar campos de data + const camposData = document.querySelectorAll('input[type="date"]'); + camposData.forEach(campo => { + // Definir data máxima como hoje + const hoje = new Date().toISOString().split('T')[0]; + campo.setAttribute('max', hoje); + + campo.addEventListener('change', function() { + if (!validarData(this.value)) { + this.setCustomValidity('Data inválida ou futura'); + this.classList.add('is-invalid'); + } else { + this.setCustomValidity(''); + this.classList.remove('is-invalid'); + } + }); + }); }); \ No newline at end of file diff --git a/static/js/table_sort.js b/static/js/table_sort.js new file mode 100644 index 0000000..eda9c51 --- /dev/null +++ b/static/js/table_sort.js @@ -0,0 +1,200 @@ +// Função para converter data DD/MM/YYYY para objeto Date +function converterDataParaComparacao(dataStr) { + console.log('Convertendo data para comparação:', dataStr); + + if (!dataStr) return null; + + try { + // Se já estiver no formato ISO + if (/^\d{4}-\d{2}-\d{2}/.test(dataStr)) { + const data = new Date(dataStr); + console.log('Data ISO convertida:', data); + return data; + } + + // Se estiver no formato DD/MM/YYYY + if (/^\d{2}\/\d{2}\/\d{4}/.test(dataStr)) { + const [dia, mes, ano] = dataStr.split('/').map(Number); + const data = new Date(ano, mes - 1, dia); + console.log('Data DD/MM/YYYY convertida:', data); + return data; + } + + console.warn('Formato de data não reconhecido:', dataStr); + return null; + } catch (error) { + console.error('Erro ao converter data:', error, 'Data:', dataStr); + return null; + } +} + +// Função para ordenar tabelas +function configurarOrdenacaoTabela(tabelaId) { + console.log('Configurando ordenação para tabela:', tabelaId); + + const table = document.getElementById(tabelaId); + if (!table) { + console.warn('Tabela não encontrada:', tabelaId); + return; + } + + const headers = table.querySelectorAll('th[data-sort]'); + headers.forEach(header => { + if (header.dataset.sort) { + header.addEventListener('click', () => { + const column = header.dataset.sort; + const tbody = table.getElementsByTagName('tbody')[0]; + const rows = Array.from(tbody.getElementsByTagName('tr')); + + console.log('Ordenando coluna:', column); + + rows.sort((a, b) => { + const aValue = a.querySelector(`td[data-${column}]`).dataset[column]; + const bValue = b.querySelector(`td[data-${column}]`).dataset[column]; + + // Ordenação por data + if (column === 'data' || + column === 'data_vencimento' || + column === 'data_alteracao' || + column === 'data_pagamento' || + column === 'data_venda' || + column === 'data_relatorio') { + const aDate = converterDataParaComparacao(aValue); + const bDate = converterDataParaComparacao(bValue); + + // Se alguma data for inválida + if (!aDate && !bDate) return 0; + if (!aDate) return 1; + if (!bDate) return -1; + + return aDate - bDate; + } + + // Ordenação por valor monetário + if (column === 'valor' || + column === 'valor_total' || + column === 'valor_antigo' || + column === 'valor_novo') { + const aNum = parseFloat(aValue.replace(/[^\d,-]/g, '').replace(',', '.')); + const bNum = parseFloat(bValue.replace(/[^\d,-]/g, '').replace(',', '.')); + return aNum - bNum; + } + + // Ordenação padrão para texto + return aValue.localeCompare(bValue); + }); + + // Alternar direção da ordenação + if (header.classList.contains('asc')) { + rows.reverse(); + header.classList.remove('asc'); + header.classList.add('desc'); + console.log('Ordenação descendente'); + } else { + header.classList.remove('desc'); + header.classList.add('asc'); + console.log('Ordenação ascendente'); + } + + // Atualizar tabela + tbody.innerHTML = ''; + rows.forEach(row => tbody.appendChild(row)); + }); + } + }); +} + +// Configurar ordenação para todas as tabelas que precisam +document.addEventListener('DOMContentLoaded', function() { + console.log('Configurando ordenação para todas as tabelas...'); + + const tabelas = [ + 'materiaisTable', + 'vendasTable', + 'cotasTable', + 'pagamentosTable' + ]; + + tabelas.forEach(tabelaId => { + configurarOrdenacaoTabela(tabelaId); + }); +}); + +document.addEventListener('DOMContentLoaded', function() { + console.log('Carregando script table_sort.js...'); + + // Função para comparar datas no formato DD/MM/YYYY + function compararDatas(a, b) { + if (!a || !b) return 0; + + const [diaA, mesA, anoA] = a.split('/').map(Number); + const [diaB, mesB, anoB] = b.split('/').map(Number); + + const dataA = new Date(anoA, mesA - 1, diaA); + const dataB = new Date(anoB, mesB - 1, diaB); + + return dataA - dataB; + } + + // Função para comparar valores monetários + function compararValores(a, b) { + const valorA = parseFloat(a.replace('R$ ', '').replace('.', '').replace(',', '.')); + const valorB = parseFloat(b.replace('R$ ', '').replace('.', '').replace(',', '.')); + + if (isNaN(valorA)) return -1; + if (isNaN(valorB)) return 1; + + return valorA - valorB; + } + + // Configurar ordenação para todas as tabelas com classe 'table-sort' + document.querySelectorAll('table.table-sort').forEach(tabela => { + const tbody = tabela.querySelector('tbody'); + const headers = tabela.querySelectorAll('th[data-sort]'); + + headers.forEach(header => { + const tipoOrdenacao = header.dataset.sort; + + header.addEventListener('click', () => { + const rows = Array.from(tbody.querySelectorAll('tr')); + const colIndex = Array.from(header.parentNode.children).indexOf(header); + + rows.sort((rowA, rowB) => { + const cellA = rowA.children[colIndex].dataset[tipoOrdenacao] || rowA.children[colIndex].textContent.trim(); + const cellB = rowB.children[colIndex].dataset[tipoOrdenacao] || rowB.children[colIndex].textContent.trim(); + + switch (tipoOrdenacao) { + case 'data': + return compararDatas(cellA, cellB); + case 'valor': + return compararValores(cellA, cellB); + case 'numero': + return parseFloat(cellA) - parseFloat(cellB); + default: + return cellA.localeCompare(cellB); + } + }); + + if (header.classList.contains('asc')) { + rows.reverse(); + header.classList.remove('asc'); + header.classList.add('desc'); + } else { + header.classList.remove('desc'); + header.classList.add('asc'); + } + + // Remover classes de ordenação de outros headers + headers.forEach(h => { + if (h !== header) { + h.classList.remove('asc', 'desc'); + } + }); + + // Atualizar tabela + tbody.innerHTML = ''; + rows.forEach(row => tbody.appendChild(row)); + }); + }); + }); +}); \ No newline at end of file diff --git a/static/js/testes.js b/static/js/testes.js new file mode 100644 index 0000000..45ae24f --- /dev/null +++ b/static/js/testes.js @@ -0,0 +1,284 @@ +// Testes para o formulário de edição de militantes +console.log('Iniciando testes do formulário de edição...'); + +// Lista de campos que devem existir no formulário +const camposEsperados = { + 'edit_militante_id': { tipo: 'hidden', obrigatorio: true }, + 'edit_nome': { tipo: 'text', obrigatorio: true }, + 'edit_cpf': { tipo: 'text', obrigatorio: true }, + 'edit_titulo_eleitoral': { tipo: 'text', obrigatorio: false }, + 'edit_data_nascimento': { tipo: 'text', obrigatorio: false }, + 'edit_data_entrada_oci': { tipo: 'text', obrigatorio: false }, + 'edit_data_efetivacao_oci': { tipo: 'text', obrigatorio: false }, + 'edit_email': { tipo: 'email', obrigatorio: true }, + 'edit_telefone1': { tipo: 'text', obrigatorio: false }, + 'edit_telefone2': { tipo: 'text', obrigatorio: false }, + 'edit_cep': { tipo: 'text', obrigatorio: false }, + 'edit_estado': { tipo: 'select', obrigatorio: false }, + 'edit_cidade': { tipo: 'text', obrigatorio: false }, + 'edit_bairro': { tipo: 'text', obrigatorio: false }, + 'edit_rua': { tipo: 'text', obrigatorio: false }, + 'edit_numero': { tipo: 'text', obrigatorio: false }, + 'edit_complemento': { tipo: 'text', obrigatorio: false }, + 'edit_empresa': { tipo: 'text', obrigatorio: false }, + 'edit_contratante': { tipo: 'text', obrigatorio: false }, + 'edit_instituicao_ensino': { tipo: 'text', obrigatorio: false }, + 'edit_tipo_instituicao': { tipo: 'select', obrigatorio: false }, + 'edit_sindicato': { tipo: 'text', obrigatorio: false }, + 'edit_cargo_sindical': { tipo: 'text', obrigatorio: false }, + 'edit_central_sindical': { tipo: 'text', obrigatorio: false }, + 'edit_celula': { tipo: 'select', obrigatorio: false }, + 'responsabilidades_values': { tipo: 'hidden', obrigatorio: false } +}; + +// Função para testar a existência e configuração dos campos +function testarCamposFormulario() { + console.log('Testando campos do formulário...'); + const form = document.getElementById('formEditarMilitante'); + const erros = []; + + if (!form) { + console.error('Formulário não encontrado!'); + return false; + } + + // Testar cada campo esperado + for (const [id, config] of Object.entries(camposEsperados)) { + const campo = document.getElementById(id); + if (!campo) { + erros.push(`Campo ${id} não encontrado`); + continue; + } + + // Verificar tipo + if (campo.type !== config.tipo && config.tipo !== 'select') { + erros.push(`Campo ${id} tem tipo ${campo.type}, esperado ${config.tipo}`); + } + + // Verificar obrigatoriedade + if (config.obrigatorio && !campo.hasAttribute('required')) { + erros.push(`Campo ${id} deveria ser obrigatório`); + } + + // Verificar se o campo tem name attribute + if (!campo.hasAttribute('name')) { + erros.push(`Campo ${id} não tem atributo name`); + } + } + + // Reportar erros encontrados + if (erros.length > 0) { + console.error('Erros encontrados nos campos:', erros); + return false; + } + + console.log('Todos os campos estão configurados corretamente'); + return true; +} + +// Função para testar o carregamento de dados +async function testarCarregamentoDados(militanteId) { + console.log('Testando carregamento de dados...'); + try { + const response = await fetch(`/militantes/dados/${militanteId}`); + if (!response.ok) { + throw new Error(`Erro HTTP: ${response.status}`); + } + + const data = await response.json(); + console.log('Dados recebidos:', data); + + // Verificar se os dados foram carregados corretamente + const erros = []; + + // Verificar campos básicos + if (!data.nome) erros.push('Nome não carregado'); + if (!data.cpf) erros.push('CPF não carregado'); + + // Verificar se os campos foram preenchidos + for (const [id, config] of Object.entries(camposEsperados)) { + const campo = document.getElementById(id); + if (!campo) continue; + + // Mapear campos do servidor para campos do formulário + let valorEsperado = ''; + switch(id) { + case 'edit_nome': valorEsperado = data.nome; break; + case 'edit_cpf': valorEsperado = data.cpf; break; + case 'edit_email': valorEsperado = data.emails?.[0]; break; + case 'edit_telefone1': valorEsperado = data.telefone1; break; + case 'edit_celula': valorEsperado = data.celula_id?.toString(); break; + case 'edit_cargo_sindical': valorEsperado = data.cargo_sindical; break; + case 'edit_central_sindical': valorEsperado = data.central_sindical; break; + case 'edit_sindicato': valorEsperado = data.sindicato; break; + // Adicione mais campos conforme necessário + } + + if (config.obrigatorio && !valorEsperado) { + erros.push(`Campo obrigatório ${id} não tem valor no servidor`); + } + + if (valorEsperado && campo.value !== valorEsperado) { + erros.push(`Campo ${id} tem valor diferente do servidor. Esperado: ${valorEsperado}, Atual: ${campo.value}`); + } + } + + if (erros.length > 0) { + console.error('Erros no carregamento:', erros); + return false; + } + + console.log('Dados carregados corretamente'); + return true; + } catch (error) { + console.error('Erro ao carregar dados:', error); + return false; + } +} + +// Função para testar o salvamento de dados +async function testarSalvamentoDados(militanteId) { + console.log('Testando salvamento de dados...'); + try { + const form = document.getElementById('formEditarMilitante'); + const formData = new FormData(form); + + // Guardar valores originais para comparação + const valoresOriginais = { + nome: formData.get('nome'), + cpf: formData.get('cpf'), + email: formData.get('email'), + celula: formData.get('celula'), + cargo_sindical: formData.get('cargo_sindical'), + central_sindical: formData.get('central_sindical'), + sindicato: formData.get('sindicato'), + responsabilidades: formData.get('responsabilidades_values') + }; + + const response = await fetch(`/militantes/editar/${militanteId}`, { + method: 'POST', + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') + }, + body: formData + }); + + if (!response.ok) { + throw new Error(`Erro HTTP: ${response.status}`); + } + + const data = await response.json(); + console.log('Resposta do servidor:', data); + + // Verificar se os dados foram salvos corretamente + const row = document.querySelector(`tr[data-militante="${militanteId}"]`); + if (!row) { + console.error('Linha da tabela não encontrada após salvamento'); + return false; + } + + const erros = []; + + // Verificar dados básicos na tabela + const nome = row.querySelector('td[data-nome]')?.textContent; + const cpf = row.querySelector('td[data-cpf]')?.textContent; + const email = row.querySelector('td[data-email]')?.textContent; + + if (nome !== valoresOriginais.nome) erros.push(`Nome não atualizado na tabela. Esperado: ${valoresOriginais.nome}, Atual: ${nome}`); + if (cpf !== valoresOriginais.cpf) erros.push(`CPF não atualizado na tabela. Esperado: ${valoresOriginais.cpf}, Atual: ${cpf}`); + if (email !== valoresOriginais.email) erros.push(`Email não atualizado na tabela. Esperado: ${valoresOriginais.email}, Atual: ${email}`); + + // Verificar atributos para filtros + const celulaId = row.getAttribute('data-celula-id'); + const responsabilidades = row.getAttribute('data-responsabilidades'); + + if (celulaId !== valoresOriginais.celula) erros.push(`Célula não atualizada na tabela. Esperado: ${valoresOriginais.celula}, Atual: ${celulaId}`); + if (responsabilidades !== valoresOriginais.responsabilidades) erros.push(`Responsabilidades não atualizadas na tabela. Esperado: ${valoresOriginais.responsabilidades}, Atual: ${responsabilidades}`); + + // Verificar botão de edição + const btnEditar = row.querySelector('button[data-bs-target="#modalEditarMilitante"]'); + if (btnEditar) { + if (btnEditar.getAttribute('data-militante-nome') !== valoresOriginais.nome) { + erros.push('Nome não atualizado no botão de edição'); + } + if (btnEditar.getAttribute('data-celula-id') !== valoresOriginais.celula) { + erros.push('Célula não atualizada no botão de edição'); + } + } + + if (erros.length > 0) { + console.error('Erros no salvamento:', erros); + return false; + } + + console.log('Dados salvos e atualizados corretamente'); + return true; + } catch (error) { + console.error('Erro ao salvar dados:', error); + return false; + } +} + +// Função principal de teste +async function testarFormularioEdicao(militanteId) { + console.log('Iniciando teste completo do formulário...'); + + // Testar campos do formulário + if (!testarCamposFormulario()) { + console.error('Teste dos campos falhou'); + return false; + } + + // Testar carregamento de dados + if (!await testarCarregamentoDados(militanteId)) { + console.error('Teste de carregamento falhou'); + return false; + } + + // Testar salvamento de dados + if (!await testarSalvamentoDados(militanteId)) { + console.error('Teste de salvamento falhou'); + return false; + } + + console.log('Todos os testes passaram com sucesso!'); + return true; +} + +// Executar testes quando o documento estiver carregado +document.addEventListener('DOMContentLoaded', function() { + // Adicionar botão de teste na interface + const btnTeste = document.createElement('button'); + btnTeste.className = 'btn btn-info me-2'; + btnTeste.innerHTML = 'Testar Formulário'; + btnTeste.onclick = function() { + // Pegar ID do primeiro militante da lista + const primeiraLinha = document.querySelector('#militantesTable tbody tr'); + if (!primeiraLinha) { + mostrarAlerta('danger', 'Nenhum militante encontrado para teste'); + return; + } + + const militanteId = primeiraLinha.getAttribute('data-militante'); + if (!militanteId) { + mostrarAlerta('danger', 'ID do militante não encontrado'); + return; + } + + // Executar testes + testarFormularioEdicao(militanteId).then(sucesso => { + if (sucesso) { + mostrarAlerta('success', 'Testes concluídos com sucesso!'); + } else { + mostrarAlerta('danger', 'Alguns testes falharam. Verifique o console para mais detalhes.'); + } + }); + }; + + // Adicionar botão ao lado do botão de exportar + const btnExportar = document.querySelector('.btn-exportar'); + if (btnExportar && btnExportar.parentNode) { + btnExportar.parentNode.insertBefore(btnTeste, btnExportar); + } +}); \ No newline at end of file diff --git a/static/js/vendas.js b/static/js/vendas.js new file mode 100644 index 0000000..5560f33 --- /dev/null +++ b/static/js/vendas.js @@ -0,0 +1,119 @@ +document.addEventListener('DOMContentLoaded', function() { + console.log('Carregando script vendas.js...'); + + // Funções de validação e formatação de datas + function validarData(data) { + if (!data) return false; + + const dataObj = new Date(data); + if (isNaN(dataObj.getTime())) return false; + + const hoje = new Date(); + hoje.setHours(0, 0, 0, 0); + + return dataObj <= hoje; + } + + function formatarData(data) { + if (!data) return ''; + + const dataObj = new Date(data); + if (isNaN(dataObj.getTime())) return ''; + + return dataObj.toLocaleDateString('pt-BR'); + } + + // Configurar campos de data + const camposData = document.querySelectorAll('input[type="date"]'); + camposData.forEach(campo => { + // Definir data máxima como hoje + const hoje = new Date().toISOString().split('T')[0]; + campo.setAttribute('max', hoje); + + campo.addEventListener('change', function() { + if (!validarData(this.value)) { + this.setCustomValidity('Data inválida ou futura'); + this.classList.add('is-invalid'); + } else { + this.setCustomValidity(''); + this.classList.remove('is-invalid'); + } + }); + }); + + // Configurar tabela de vendas + const tabelaVendas = $('#vendasTable').DataTable({ + language: { + url: '//cdn.datatables.net/plug-ins/1.13.7/i18n/pt-BR.json' + }, + columnDefs: [ + { + targets: 3, // Coluna de data + type: 'date-br', + render: function(data, type, row) { + if (type === 'sort') { + return data.split('/').reverse().join(''); + } + return data; + } + }, + { + targets: 2, // Coluna de valor + type: 'numeric', + render: function(data, type, row) { + if (type === 'sort') { + return parseFloat(data.replace('R$ ', '').replace(',', '.')); + } + return data; + } + }, + { targets: -1, orderable: false } // Coluna de ações + ], + order: [[3, 'desc']] // Ordenar por data decrescente por padrão + }); + + // Atualizar valor total ao mudar quantidade ou material + const campoQuantidade = document.getElementById('quantidade'); + const campoMaterial = document.getElementById('material_id'); + const campoValorTotal = document.getElementById('valor_total'); + + function atualizarValorTotal() { + if (!campoQuantidade || !campoMaterial || !campoValorTotal) return; + + const quantidade = parseInt(campoQuantidade.value) || 0; + const materialSelecionado = campoMaterial.options[campoMaterial.selectedIndex]; + const preco = materialSelecionado ? parseFloat(materialSelecionado.dataset.preco) || 0 : 0; + + campoValorTotal.value = (quantidade * preco).toFixed(2); + } + + if (campoQuantidade) { + campoQuantidade.addEventListener('change', atualizarValorTotal); + } + if (campoMaterial) { + campoMaterial.addEventListener('change', atualizarValorTotal); + } + + // Configurar modal de edição + const modalEditarVenda = document.getElementById('modalEditarVenda'); + if (modalEditarVenda) { + modalEditarVenda.addEventListener('show.bs.modal', function(event) { + const button = event.relatedTarget; + if (!button) return; + + const vendaId = button.getAttribute('data-venda-id'); + const militanteId = button.getAttribute('data-militante-id'); + const materialId = button.getAttribute('data-material-id'); + const quantidade = button.getAttribute('data-quantidade'); + const valorTotal = button.getAttribute('data-valor-total'); + const dataVenda = button.getAttribute('data-data-venda'); + + document.getElementById('editVendaId').value = vendaId; + document.getElementById('editMilitanteId').value = militanteId; + document.getElementById('editMaterialId').value = materialId; + document.getElementById('editQuantidade').value = quantidade; + document.getElementById('editValorTotal').value = valorTotal; + document.getElementById('editDataVenda').value = dataVenda; + }); + } +}); \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index eab4aea..9327f54 100644 --- a/templates/base.html +++ b/templates/base.html @@ -4,6 +4,7 @@ + {% block title %}{% endblock %} - Controles OCI diff --git a/templates/listar_militantes.html b/templates/listar_militantes.html index 745d975..0c84c60 100644 --- a/templates/listar_militantes.html +++ b/templates/listar_militantes.html @@ -63,7 +63,7 @@
  • {% for celula in celulas %} -
  • {{ celula.nome }}
  • +
  • {{ celula.nome }}
  • {% endfor %} diff --git a/templates/listar_relatorios_cotas.html b/templates/listar_relatorios_cotas.html index 29e2d4a..3296044 100644 --- a/templates/listar_relatorios_cotas.html +++ b/templates/listar_relatorios_cotas.html @@ -3,47 +3,47 @@ {% block title %}Listar Relatórios de Cotas{% endblock %} {% block content %} -
    -
    -
    -

    Lista de Relatórios de Cotas

    - - {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -
    {{ message }}
    - {% endfor %} - {% endif %} - {% endwith %} - - - +
    +
    +
    +
    Relatórios de Cotas
    + + Novo Relatório + +
    +
    - +
    - - - - - - + + + + + + {% for relatorio in relatorios %} - - - - - - + + + + + {% endfor %} @@ -53,5 +53,7 @@ + + {% endblock %} diff --git a/templates/listar_relatorios_vendas.html b/templates/listar_relatorios_vendas.html index dfe7a25..69ca341 100644 --- a/templates/listar_relatorios_vendas.html +++ b/templates/listar_relatorios_vendas.html @@ -3,47 +3,47 @@ {% block title %}Listar Relatórios de Vendas{% endblock %} {% block content %} -
    -
    -
    -

    Lista de Relatórios de Vendas

    - - {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -
    {{ message }}
    - {% endfor %} - {% endif %} - {% endwith %} - - - +
    +
    +
    +
    Relatórios de Vendas
    + + Novo Relatório + +
    +
    -
    IDSetorComitê CentralTotal de CotasData do RelatórioAçõesID Setor Comitê Central Total de Cotas Data do Relatório Ações
    {{ relatorio.id }}{{ relatorio.setor.nome }}{{ relatorio.comite.nome }}R$ {{ "%.2f"|format(relatorio.total_cotas) }}{{ relatorio.data_relatorio.strftime('%d/%m/%Y') }} - Editar - Excluir + {{ relatorio.id }}{{ relatorio.setor.nome }}{{ relatorio.comite.nome }}R$ {{ "%.2f"|format(relatorio.total_cotas) }}{{ relatorio.data_relatorio.strftime('%d/%m/%Y') }} + + + + + +
    +
    - - - - - - + + + + + + {% for relatorio in relatorios %} - - - - - - + + + + + {% endfor %} @@ -53,4 +53,6 @@ + + {% endblock %} diff --git a/templates/militantes.html b/templates/militantes.html index 99b567e..a5ffe51 100644 --- a/templates/militantes.html +++ b/templates/militantes.html @@ -20,4 +20,16 @@ - \ No newline at end of file + + +{% include 'modals/militante_editar.html' %} +{% include 'modals/militante_excluir.html' %} + + + +{% if config.DEBUG %} + + +{% endif %} + + \ No newline at end of file diff --git a/templates/modals/militante_editar.html b/templates/modals/militante_editar.html index c58defa..e04da6b 100644 --- a/templates/modals/militante_editar.html +++ b/templates/modals/militante_editar.html @@ -1,249 +1,275 @@ -
    IDSetorComitê CentralTotal de VendasData do RelatórioAçõesID Setor Comitê Central Total de Vendas Data do Relatório Ações
    {{ relatorio.id }}{{ relatorio.setor.nome }}{{ relatorio.comite.nome }}R$ {{ "%.2f"|format(relatorio.total_vendas) }}{{ relatorio.data_relatorio.strftime('%d/%m/%Y') }} - Editar - Excluir + {{ relatorio.id }}{{ relatorio.setor.nome }}{{ relatorio.comite.nome }}R$ {{ "%.2f"|format(relatorio.total_vendas) }}{{ relatorio.data_relatorio.strftime('%d/%m/%Y') }} + + + + + +