fix: corrige problemas de permissões e rotas
This commit is contained in:
78
app.py
78
app.py
@@ -60,6 +60,12 @@ login_manager = LoginManager()
|
|||||||
login_manager.init_app(app)
|
login_manager.init_app(app)
|
||||||
login_manager.login_view = 'login'
|
login_manager.login_view = 'login'
|
||||||
|
|
||||||
|
# Adicionar filtros Jinja2
|
||||||
|
@app.template_filter('bitwise_and')
|
||||||
|
def bitwise_and(value1, value2):
|
||||||
|
"""Filtro para operação bit a bit AND"""
|
||||||
|
return value1 & value2
|
||||||
|
|
||||||
@login_manager.user_loader
|
@login_manager.user_loader
|
||||||
def load_user(user_id):
|
def load_user(user_id):
|
||||||
"""Carrega o usuário pelo ID"""
|
"""Carrega o usuário pelo ID"""
|
||||||
@@ -381,20 +387,12 @@ def criar_militante():
|
|||||||
# Rota para listar militantes
|
# Rota para listar militantes
|
||||||
@app.route("/militantes")
|
@app.route("/militantes")
|
||||||
@require_login
|
@require_login
|
||||||
@require_permission(Permission.VIEW_CELL_DATA)
|
@require_permission(Permission.MANAGE_CELL_MEMBERS)
|
||||||
def listar_militantes():
|
def listar_militantes():
|
||||||
|
db = get_db_connection()
|
||||||
try:
|
try:
|
||||||
print("Buscando militantes...")
|
militantes = db.query(Militante).all()
|
||||||
db = get_db_connection()
|
return render_template('listar_militantes.html', militantes=militantes)
|
||||||
militantes = db.query(Militante).order_by(Militante.nome).all()
|
|
||||||
print(f"Total de militantes encontrados: {len(militantes)}")
|
|
||||||
for militante in militantes:
|
|
||||||
print(f"Militante: {militante.nome} (ID: {militante.id})")
|
|
||||||
return render_template("listar_militantes.html", militantes=militantes)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Erro ao listar militantes: {e}")
|
|
||||||
flash('Erro ao listar militantes', 'danger')
|
|
||||||
return render_template("listar_militantes.html", militantes=[])
|
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
@@ -424,7 +422,7 @@ def excluir_militante(id):
|
|||||||
# Rota para criar uma nova cota
|
# Rota para criar uma nova cota
|
||||||
@app.route("/cotas/novo", methods=["GET", "POST"])
|
@app.route("/cotas/novo", methods=["GET", "POST"])
|
||||||
@require_login
|
@require_login
|
||||||
@require_permission(Permission.VIEW_CELL_REPORTS)
|
@require_permission(Permission.MANAGE_CELL_REPORTS)
|
||||||
def nova_cota():
|
def nova_cota():
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
militante_id = request.form.get("militante_id")
|
militante_id = request.form.get("militante_id")
|
||||||
@@ -479,19 +477,16 @@ def nova_cota():
|
|||||||
# Rota para listar cotas
|
# Rota para listar cotas
|
||||||
@app.route("/cotas")
|
@app.route("/cotas")
|
||||||
@require_login
|
@require_login
|
||||||
@require_permission(Permission.VIEW_CELL_REPORTS)
|
@require_permission(Permission.MANAGE_CELL_REPORTS)
|
||||||
def listar_cotas():
|
def listar_cotas():
|
||||||
try:
|
try:
|
||||||
print("Buscando cotas...")
|
print("Buscando cotas...")
|
||||||
# Buscar cotas com informações do militante
|
|
||||||
db = get_db_connection()
|
db = get_db_connection()
|
||||||
cotas = db.query(CotaMensal)\
|
cotas = db.query(CotaMensal)\
|
||||||
.join(Militante)\
|
.join(Militante)\
|
||||||
.order_by(CotaMensal.data_vencimento.desc())\
|
.order_by(CotaMensal.data_vencimento.desc())\
|
||||||
.all()
|
.all()
|
||||||
|
|
||||||
print(f"Total de cotas encontradas: {len(cotas)}")
|
|
||||||
|
|
||||||
# Calcular status de cada cota
|
# Calcular status de cada cota
|
||||||
for cota in cotas:
|
for cota in cotas:
|
||||||
if cota.pago:
|
if cota.pago:
|
||||||
@@ -500,7 +495,6 @@ def listar_cotas():
|
|||||||
cota.status = "atrasada"
|
cota.status = "atrasada"
|
||||||
else:
|
else:
|
||||||
cota.status = "pendente"
|
cota.status = "pendente"
|
||||||
print(f"Cota {cota.id}: Militante={cota.militante.nome}, Valor={cota.valor_novo}, Status={cota.status}")
|
|
||||||
|
|
||||||
# Buscar militantes para o modal de nova cota
|
# Buscar militantes para o modal de nova cota
|
||||||
militantes = db.query(Militante).order_by(Militante.nome).all()
|
militantes = db.query(Militante).order_by(Militante.nome).all()
|
||||||
@@ -622,18 +616,60 @@ def novo_pagamento():
|
|||||||
# Rota para listar pagamentos
|
# Rota para listar pagamentos
|
||||||
@app.route("/pagamentos")
|
@app.route("/pagamentos")
|
||||||
@require_login
|
@require_login
|
||||||
|
@require_permission(Permission.MANAGE_CELL_REPORTS)
|
||||||
def listar_pagamentos():
|
def listar_pagamentos():
|
||||||
try:
|
try:
|
||||||
|
print("Buscando pagamentos...")
|
||||||
db = get_db_connection()
|
db = get_db_connection()
|
||||||
pagamentos = db.query(Pagamento).order_by(Pagamento.data_pagamento.desc()).all()
|
pagamentos = db.query(Pagamento)\
|
||||||
return render_template("listar_pagamentos.html", pagamentos=pagamentos)
|
.join(Militante)\
|
||||||
|
.order_by(Pagamento.data_pagamento.desc())\
|
||||||
|
.all()
|
||||||
|
|
||||||
|
militantes = db.query(Militante).order_by(Militante.nome).all()
|
||||||
|
tipos_pagamento = db.query(TipoPagamento).all()
|
||||||
|
|
||||||
|
return render_template("listar_pagamentos.html",
|
||||||
|
pagamentos=pagamentos,
|
||||||
|
militantes=militantes,
|
||||||
|
tipos_pagamento=tipos_pagamento)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Erro ao listar pagamentos: {e}")
|
print(f"Erro ao listar pagamentos: {e}")
|
||||||
flash('Erro ao listar pagamentos', 'danger')
|
flash('Erro ao listar pagamentos', 'danger')
|
||||||
return render_template("listar_pagamentos.html", pagamentos=[])
|
return render_template("listar_pagamentos.html", pagamentos=[], militantes=[])
|
||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
# Rota para adicionar pagamento
|
||||||
|
@app.route("/pagamentos/adicionar", methods=["POST"])
|
||||||
|
@require_login
|
||||||
|
@require_permission(Permission.MANAGE_CELL_REPORTS)
|
||||||
|
def adicionar_pagamento():
|
||||||
|
if request.method == "POST":
|
||||||
|
try:
|
||||||
|
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()
|
||||||
|
|
||||||
|
db = get_db_connection()
|
||||||
|
pagamento = Pagamento(
|
||||||
|
militante_id=militante_id,
|
||||||
|
tipo_pagamento=tipo_pagamento,
|
||||||
|
valor=valor,
|
||||||
|
data_pagamento=data_pagamento
|
||||||
|
)
|
||||||
|
db.add(pagamento)
|
||||||
|
db.commit()
|
||||||
|
flash('Pagamento adicionado com sucesso!', 'success')
|
||||||
|
except Exception as e:
|
||||||
|
db.rollback()
|
||||||
|
flash(f'Erro ao adicionar pagamento: {str(e)}', 'danger')
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
return redirect(url_for('listar_pagamentos'))
|
||||||
|
|
||||||
# Rota para criar um novo material vendido
|
# Rota para criar um novo material vendido
|
||||||
@app.route("/materiais/novo", methods=["GET", "POST"])
|
@app.route("/materiais/novo", methods=["GET", "POST"])
|
||||||
@require_login
|
@require_login
|
||||||
|
|||||||
@@ -507,7 +507,11 @@ class Usuario(Base, UserMixin):
|
|||||||
return time_diff.total_seconds() > (self.session_timeout * 60)
|
return time_diff.total_seconds() > (self.session_timeout * 60)
|
||||||
|
|
||||||
def has_permission(self, permission_name):
|
def has_permission(self, permission_name):
|
||||||
"""Verifica se o usuário tem uma determinada permissão"""
|
"""Verifica se o usuário tem uma permissão específica"""
|
||||||
|
if self.is_admin: # Se for admin, tem todas as permissões
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Verifica se o usuário tem a permissão através de suas roles
|
||||||
for role in self.roles:
|
for role in self.roles:
|
||||||
for permission in role.permissions:
|
for permission in role.permissions:
|
||||||
if permission.nome == permission_name:
|
if permission.nome == permission_name:
|
||||||
|
|||||||
@@ -137,14 +137,22 @@ def init_rbac():
|
|||||||
session = get_db_connection()
|
session = get_db_connection()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Criar roles se não existirem
|
# Criar role de administrador primeiro
|
||||||
for nivel, nome in Role.get_roles_list():
|
admin_role = session.query(Role).filter_by(nome="Administrador").first()
|
||||||
role = session.query(Role).filter_by(nivel=nivel).first()
|
if not admin_role:
|
||||||
if not role:
|
admin_role = Role(nome="Administrador", nivel=Role.SECRETARIO_GERAL)
|
||||||
role = Role(nome=nome, nivel=nivel)
|
session.add(admin_role)
|
||||||
session.add(role)
|
session.commit()
|
||||||
|
|
||||||
# Criar permissões se não existirem
|
# Criar outras roles
|
||||||
|
for nivel, nome in Role.get_roles_list():
|
||||||
|
if nome != "Administrador": # Pular Administrador pois já foi criado
|
||||||
|
role = session.query(Role).filter_by(nivel=nivel).first()
|
||||||
|
if not role:
|
||||||
|
role = Role(nome=nome, nivel=nivel)
|
||||||
|
session.add(role)
|
||||||
|
|
||||||
|
# Criar permissões
|
||||||
for nome, descricao in Permission.get_permissions_list():
|
for nome, descricao in Permission.get_permissions_list():
|
||||||
permission = session.query(Permission).filter_by(nome=nome).first()
|
permission = session.query(Permission).filter_by(nome=nome).first()
|
||||||
if not permission:
|
if not permission:
|
||||||
@@ -153,13 +161,6 @@ def init_rbac():
|
|||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
# Buscar role de administrador
|
|
||||||
admin_role = session.query(Role).filter_by(nome="Administrador").first()
|
|
||||||
if not admin_role:
|
|
||||||
admin_role = Role(nome="Administrador", nivel=Role.SECRETARIO_GERAL)
|
|
||||||
session.add(admin_role)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
# Dar todas as permissões para o admin
|
# Dar todas as permissões para o admin
|
||||||
all_permissions = session.query(Permission).all()
|
all_permissions = session.query(Permission).all()
|
||||||
admin_role.permissions = all_permissions
|
admin_role.permissions = all_permissions
|
||||||
@@ -167,9 +168,10 @@ def init_rbac():
|
|||||||
|
|
||||||
# Buscar usuário admin e atribuir role de administrador
|
# Buscar usuário admin e atribuir role de administrador
|
||||||
admin_user = session.query(Usuario).filter_by(username="admin").first()
|
admin_user = session.query(Usuario).filter_by(username="admin").first()
|
||||||
if admin_user and admin_role not in admin_user.roles:
|
if admin_user:
|
||||||
admin_user.roles.append(admin_role)
|
if admin_role not in admin_user.roles:
|
||||||
session.commit()
|
admin_user.roles = [admin_role] # Substituir roles existentes
|
||||||
|
session.commit()
|
||||||
|
|
||||||
# Mapear permissões para outros roles
|
# Mapear permissões para outros roles
|
||||||
for role in session.query(Role).filter(Role.nome != "Administrador").all():
|
for role in session.query(Role).filter(Role.nome != "Administrador").all():
|
||||||
|
|||||||
@@ -3,104 +3,44 @@
|
|||||||
{% block title %}Militantes{% endblock %}
|
{% block title %}Militantes{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<<<<<<< HEAD
|
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<h1 class="mb-0">
|
<h1 class="h3 mb-0">
|
||||||
<i class="fas fa-users me-2"></i>Militantes
|
<i class="fas fa-users me-2"></i>Militantes
|
||||||
</h1>
|
</h1>
|
||||||
{% if current_user.has_permission('gerenciar_militantes') %}
|
{% if current_user.has_permission('gerenciar_militantes') %}
|
||||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalNovoMilitante">
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalNovoMilitante">
|
||||||
<i class="fas fa-user-plus me-2"></i>Novo Militante
|
<i class="fas fa-user-plus me-2"></i>Novo Militante
|
||||||
=======
|
|
||||||
<div class="table-container">
|
|
||||||
<div class="list-header">
|
|
||||||
<h2 class="list-title">
|
|
||||||
<i class="fas fa-users"></i>Militantes
|
|
||||||
</h2>
|
|
||||||
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#modalNovoMilitante">
|
|
||||||
<i class="fas fa-user-plus me-2"></i>Novo Militante
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="search-bar">
|
|
||||||
<div class="search-input-group input-group">
|
|
||||||
<span class="input-group-text">
|
|
||||||
<i class="fas fa-search"></i>
|
|
||||||
</span>
|
|
||||||
<input type="text" class="form-control" placeholder="Pesquisar militantes..." id="searchInput">
|
|
||||||
</div>
|
|
||||||
<div class="list-actions">
|
|
||||||
<button class="btn btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#filterCollapse">
|
|
||||||
<i class="fas fa-filter me-2"></i>Filtrar
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-outline-primary" type="button">
|
|
||||||
<i class="fas fa-file-export me-2"></i>Exportar
|
|
||||||
>>>>>>> ee71e7b (feat: padroniza componentes e estilos globais - Cria components.css, ajusta cores e organiza modais)
|
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="card">
|
||||||
<table class="table table-hover align-middle">
|
<div class="card-body">
|
||||||
<thead>
|
<div class="row mb-4">
|
||||||
<tr>
|
<div class="col-md-6">
|
||||||
<th>Nome</th>
|
<div class="input-group">
|
||||||
<th>CPF</th>
|
<span class="input-group-text">
|
||||||
<th>Email</th>
|
<i class="fas fa-search"></i>
|
||||||
<th>Telefone</th>
|
</span>
|
||||||
<th>Filiado</th>
|
<input type="text" class="form-control" id="searchInput" placeholder="Pesquisar militantes...">
|
||||||
<th class="text-end">Ações</th>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</thead>
|
<div class="col-md-6 text-end">
|
||||||
<tbody>
|
<div class="btn-group">
|
||||||
{% for militante in militantes %}
|
<button type="button" class="btn btn-outline-secondary" data-filter="todos">Todos</button>
|
||||||
<tr>
|
<button type="button" class="btn btn-outline-secondary" data-filter="filiados">Filiados</button>
|
||||||
<td>{{ militante.nome }}</td>
|
<button type="button" class="btn btn-outline-secondary" data-filter="nao-filiados">Não Filiados</button>
|
||||||
<td>{{ militante.cpf }}</td>
|
</div>
|
||||||
<td>{{ militante.email }}</td>
|
<button class="btn btn-outline-primary" type="button" id="btnExportar">
|
||||||
<td>{{ militante.telefone }}</td>
|
<i class="fas fa-file-export me-2"></i>Exportar
|
||||||
<td>
|
</button>
|
||||||
<span class="badge {{ 'bg-success' if militante.filiado else 'bg-secondary' }}">
|
</div>
|
||||||
{{ 'Sim' if militante.filiado else 'Não' }}
|
</div>
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="btn-group-actions">
|
|
||||||
<button type="button"
|
|
||||||
class="btn btn-sm btn-outline-primary"
|
|
||||||
data-bs-toggle="modal"
|
|
||||||
data-bs-target="#modalEditarMilitante"
|
|
||||||
data-militante-id="{{ militante.id }}"
|
|
||||||
data-militante-nome="{{ militante.nome }}"
|
|
||||||
data-militante-cpf="{{ militante.cpf }}"
|
|
||||||
data-militante-email="{{ militante.email }}"
|
|
||||||
data-militante-telefone="{{ militante.telefone }}"
|
|
||||||
data-militante-endereco="{{ militante.endereco }}"
|
|
||||||
data-militante-filiado="{{ militante.filiado }}"
|
|
||||||
title="Editar">
|
|
||||||
<i class="fas fa-edit"></i>
|
|
||||||
</button>
|
|
||||||
<button type="button"
|
|
||||||
class="btn btn-sm btn-outline-danger"
|
|
||||||
data-bs-toggle="modal"
|
|
||||||
data-bs-target="#deleteModal"
|
|
||||||
data-militante-id="{{ militante.id }}"
|
|
||||||
data-militante-nome="{{ militante.nome }}"
|
|
||||||
title="Excluir">
|
|
||||||
<i class="fas fa-trash"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover" id="militantesTable">
|
<table class="table table-hover" id="militantesTable">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -123,13 +63,13 @@
|
|||||||
<td data-telefone="{{ militante.telefone }}">{{ militante.telefone }}</td>
|
<td data-telefone="{{ militante.telefone }}">{{ militante.telefone }}</td>
|
||||||
<td data-celula="{{ militante.celula.nome }}">{{ militante.celula.nome }}</td>
|
<td data-celula="{{ militante.celula.nome }}">{{ militante.celula.nome }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if militante.responsabilidades & Militante.RESPONSAVEL_FINANCAS %}
|
{% if militante.responsabilidades|bitwise_and(Militante.RESPONSAVEL_FINANCAS) %}
|
||||||
<span class="badge bg-primary">Finanças</span>
|
<span class="badge bg-primary">Finanças</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if militante.responsabilidades & Militante.RESPONSAVEL_IMPRENSA %}
|
{% if militante.responsabilidades|bitwise_and(Militante.RESPONSAVEL_IMPRENSA) %}
|
||||||
<span class="badge bg-info">Imprensa</span>
|
<span class="badge bg-info">Imprensa</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if militante.responsabilidades & Militante.QUADRO_ORIENTADOR %}
|
{% if militante.responsabilidades|bitwise_and(Militante.QUADRO_ORIENTADOR) %}
|
||||||
<span class="badge bg-success">Quadro-Orientador</span>
|
<span class="badge bg-success">Quadro-Orientador</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
@@ -168,7 +108,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mt-4">
|
<div class="pagination-container">
|
||||||
<div class="text-muted">
|
<div class="text-muted">
|
||||||
Mostrando <span id="countMilitantes">{{ militantes|length }}</span> militantes
|
Mostrando <span id="countMilitantes">{{ militantes|length }}</span> militantes
|
||||||
</div>
|
</div>
|
||||||
@@ -185,403 +125,18 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
=======
|
|
||||||
<div class="pagination-container">
|
|
||||||
<div class="text-muted">
|
|
||||||
Mostrando <span id="countMilitantes">{{ militantes|length }}</span> militantes
|
|
||||||
>>>>>>> ee71e7b (feat: padroniza componentes e estilos globais - Cria components.css, ajusta cores e organiza modais)
|
|
||||||
</div>
|
|
||||||
<nav aria-label="Navegação de páginas">
|
|
||||||
<ul class="pagination mb-0">
|
|
||||||
<li class="page-item disabled" id="prevPage">
|
|
||||||
<a class="page-link" href="#"><i class="fas fa-chevron-left"></i></a>
|
|
||||||
</li>
|
|
||||||
<li class="page-item active"><a class="page-link" href="#">1</a></li>
|
|
||||||
<li class="page-item"><a class="page-link" href="#">2</a></li>
|
|
||||||
<li class="page-item"><a class="page-link" href="#">3</a></li>
|
|
||||||
<li class="page-item" id="nextPage">
|
|
||||||
<a class="page-link" href="#"><i class="fas fa-chevron-right"></i></a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
<!-- Modal de Confirmação de Exclusão -->
|
|
||||||
<div class="modal fade" id="deleteModal" tabindex="-1">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">Confirmar Exclusão</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>Tem certeza que deseja excluir o militante <strong id="militanteNome"></strong>?</p>
|
|
||||||
<p class="text-danger mb-0">Esta ação não pode ser desfeita.</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
|
||||||
<button type="button" class="btn btn-danger" id="confirmDelete">
|
|
||||||
<i class="fas fa-trash me-2"></i>Excluir
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Modal de Novo Militante -->
|
|
||||||
<div class="modal fade" id="modalNovoMilitante" tabindex="-1">
|
|
||||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">
|
|
||||||
<i class="fas fa-user-plus me-2"></i>Novo Militante
|
|
||||||
</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form id="formNovoMilitante" method="post" action="{{ url_for('novo_militante') }}">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="nome" class="form-label">Nome:</label>
|
|
||||||
<input type="text" class="form-control" id="nome" name="nome" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="cpf" class="form-label">CPF:</label>
|
|
||||||
<input type="text" class="form-control" id="cpf" name="cpf" required
|
|
||||||
pattern="\d{3}\.?\d{3}\.?\d{3}-?\d{2}"
|
|
||||||
title="Digite um CPF no formato: xxx.xxx.xxx-xx">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="email" class="form-label">Email:</label>
|
|
||||||
<input type="email" class="form-control" id="email" name="email" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="telefone" class="form-label">Telefone:</label>
|
|
||||||
<input type="text" class="form-control" id="telefone" name="telefone">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="endereco" class="form-label">Endereço:</label>
|
|
||||||
<input type="text" class="form-control" id="endereco" name="endereco">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3 form-check">
|
|
||||||
<input type="checkbox" class="form-check-input" id="filiado" name="filiado">
|
|
||||||
<label class="form-check-label" for="filiado">Filiado</label>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
|
||||||
<button type="submit" form="formNovoMilitante" class="btn btn-primary">
|
|
||||||
<i class="fas fa-save me-2"></i>Salvar
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Modal de Edição -->
|
|
||||||
<div class="modal fade" id="modalEditarMilitante" tabindex="-1">
|
|
||||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">
|
|
||||||
<i class="fas fa-user-edit me-2"></i>Editar Militante
|
|
||||||
</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form id="formEditarMilitante" method="post">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="editNome" class="form-label">Nome:</label>
|
|
||||||
<input type="text" class="form-control" id="editNome" name="nome" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="editCpf" class="form-label">CPF:</label>
|
|
||||||
<input type="text" class="form-control" id="editCpf" name="cpf" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="editEmail" class="form-label">Email:</label>
|
|
||||||
<input type="email" class="form-control" id="editEmail" name="email" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="editTelefone" class="form-label">Telefone:</label>
|
|
||||||
<input type="text" class="form-control" id="editTelefone" name="telefone">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="editEndereco" class="form-label">Endereço:</label>
|
|
||||||
<input type="text" class="form-control" id="editEndereco" name="endereco">
|
|
||||||
</div>
|
|
||||||
<div class="mb-3 form-check">
|
|
||||||
<input type="checkbox" class="form-check-input" id="editFiliado" name="filiado">
|
|
||||||
<label class="form-check-label" for="editFiliado">Filiado</label>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
|
||||||
<button type="submit" form="formEditarMilitante" class="btn btn-primary">
|
|
||||||
<i class="fas fa-save me-2"></i>Salvar
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
=======
|
|
||||||
<!-- Modais -->
|
<!-- Modais -->
|
||||||
{% include 'modals/militante_novo.html' %}
|
{% include 'modals/militante_novo.html' %}
|
||||||
{% include 'modals/militante_editar.html' %}
|
{% include 'modals/militante_editar.html' %}
|
||||||
{% include 'modals/militante_excluir.html' %}
|
{% include 'modals/militante_excluir.html' %}
|
||||||
>>>>>>> ee71e7b (feat: padroniza componentes e estilos globais - Cria components.css, ajusta cores e organiza modais)
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<<<<<<< HEAD
|
|
||||||
<script>
|
|
||||||
// Função para formatar CPF
|
|
||||||
function formatCPF(cpf) {
|
|
||||||
return cpf.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, "$1.$2.$3-$4");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Função para formatar telefone
|
|
||||||
function formatPhone(phone) {
|
|
||||||
return phone.replace(/(\d{2})(\d{5})(\d{4})/, "($1) $2-$3");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Função para carregar detalhes do militante
|
|
||||||
function loadMilitanteDetails(id) {
|
|
||||||
fetch(`/api/militante/${id}`)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
document.getElementById('militanteNome').textContent = data.nome;
|
|
||||||
document.getElementById('editNome').value = data.nome;
|
|
||||||
document.getElementById('editCpf').value = data.cpf;
|
|
||||||
document.getElementById('editEmail').value = data.email;
|
|
||||||
document.getElementById('editTelefone').value = data.telefone;
|
|
||||||
document.getElementById('editEndereco').value = data.endereco;
|
|
||||||
document.getElementById('editFiliado').checked = data.filiado;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Função para excluir militante
|
|
||||||
function deleteMilitante(id) {
|
|
||||||
fetch(`/api/militante/${id}`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.success) {
|
|
||||||
window.location.reload();
|
|
||||||
} else {
|
|
||||||
alert('Erro ao excluir militante: ' + data.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event listeners
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
// Máscara para CPF
|
|
||||||
const cpfInput = document.getElementById('cpf');
|
|
||||||
cpfInput.addEventListener('input', function(e) {
|
|
||||||
let value = e.target.value.replace(/\D/g, '');
|
|
||||||
if (value.length > 11) value = value.slice(0, 11);
|
|
||||||
e.target.value = formatCPF(value);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Máscara para telefone
|
|
||||||
const phoneInput = document.getElementById('telefone');
|
|
||||||
phoneInput.addEventListener('input', function(e) {
|
|
||||||
let value = e.target.value.replace(/\D/g, '');
|
|
||||||
if (value.length > 11) value = value.slice(0, 11);
|
|
||||||
e.target.value = formatPhone(value);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Máscara para CPF de edição
|
|
||||||
const editCpfInput = document.getElementById('editCpf');
|
|
||||||
editCpfInput.addEventListener('input', function(e) {
|
|
||||||
let value = e.target.value.replace(/\D/g, '');
|
|
||||||
if (value.length > 11) value = value.slice(0, 11);
|
|
||||||
e.target.value = formatCPF(value);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Máscara para telefone de edição
|
|
||||||
const editPhoneInput = document.getElementById('editTelefone');
|
|
||||||
editPhoneInput.addEventListener('input', function(e) {
|
|
||||||
let value = e.target.value.replace(/\D/g, '');
|
|
||||||
if (value.length > 11) value = value.slice(0, 11);
|
|
||||||
e.target.value = formatPhone(value);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Submit do formulário de edição
|
|
||||||
const formEditar = document.getElementById('formEditarMilitante');
|
|
||||||
formEditar.addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const formData = new FormData(formEditar);
|
|
||||||
const data = Object.fromEntries(formData.entries());
|
|
||||||
|
|
||||||
fetch('/api/militante/' + data.id, {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data)
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.success) {
|
|
||||||
window.location.reload();
|
|
||||||
} else {
|
|
||||||
alert('Erro ao editar militante: ' + data.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Confirmação de exclusão
|
|
||||||
const confirmDelete = document.getElementById('confirmDelete');
|
|
||||||
confirmDelete.addEventListener('click', function() {
|
|
||||||
const id = this.dataset.militanteId;
|
|
||||||
deleteMilitante(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Pesquisa
|
|
||||||
const searchInput = document.getElementById('searchInput');
|
|
||||||
searchInput.addEventListener('input', function(e) {
|
|
||||||
const searchTerm = e.target.value.toLowerCase();
|
|
||||||
const rows = document.querySelectorAll('#militantesTable tbody tr');
|
|
||||||
|
|
||||||
rows.forEach(row => {
|
|
||||||
const nome = row.querySelector('[data-nome]').textContent.toLowerCase();
|
|
||||||
const cpf = row.querySelector('[data-cpf]').textContent.toLowerCase();
|
|
||||||
const email = row.querySelector('[data-email]').textContent.toLowerCase();
|
|
||||||
const telefone = row.querySelector('[data-telefone]').textContent.toLowerCase();
|
|
||||||
const celula = row.querySelector('[data-celula]').textContent.toLowerCase();
|
|
||||||
|
|
||||||
const matches = nome.includes(searchTerm) ||
|
|
||||||
cpf.includes(searchTerm) ||
|
|
||||||
email.includes(searchTerm) ||
|
|
||||||
telefone.includes(searchTerm) ||
|
|
||||||
celula.includes(searchTerm);
|
|
||||||
|
|
||||||
row.style.display = matches ? '' : 'none';
|
|
||||||
});
|
|
||||||
|
|
||||||
updateCount();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Filtros
|
|
||||||
const filterButtons = document.querySelectorAll('[data-filter]');
|
|
||||||
filterButtons.forEach(button => {
|
|
||||||
button.addEventListener('click', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const filter = this.dataset.filter;
|
|
||||||
const rows = document.querySelectorAll('#militantesTable tbody tr');
|
|
||||||
|
|
||||||
rows.forEach(row => {
|
|
||||||
const filiado = row.dataset.filiado;
|
|
||||||
if (filter === 'todos' ||
|
|
||||||
(filter === 'filiados' && filiado === 'sim') ||
|
|
||||||
(filter === 'nao-filiados' && filiado === 'nao')) {
|
|
||||||
row.style.display = '';
|
|
||||||
} else {
|
|
||||||
row.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
updateCount();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Exportar
|
|
||||||
const btnExportar = document.getElementById('btnExportar');
|
|
||||||
btnExportar.addEventListener('click', function() {
|
|
||||||
const rows = document.querySelectorAll('#militantesTable tbody tr:not([style*="display: none"])');
|
|
||||||
const data = [];
|
|
||||||
|
|
||||||
rows.forEach(row => {
|
|
||||||
data.push({
|
|
||||||
nome: row.querySelector('[data-nome]').textContent,
|
|
||||||
cpf: row.querySelector('[data-cpf]').textContent,
|
|
||||||
email: row.querySelector('[data-email]').textContent,
|
|
||||||
telefone: row.querySelector('[data-telefone]').textContent,
|
|
||||||
celula: row.querySelector('[data-celula]').textContent
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const csv = convertToCSV(data);
|
|
||||||
downloadCSV(csv, 'militantes.csv');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ordenação
|
|
||||||
const headers = document.querySelectorAll('#militantesTable th[data-sort]');
|
|
||||||
headers.forEach(header => {
|
|
||||||
header.addEventListener('click', function() {
|
|
||||||
const sortBy = this.dataset.sort;
|
|
||||||
const rows = Array.from(document.querySelectorAll('#militantesTable tbody tr'));
|
|
||||||
const direction = this.classList.contains('asc') ? -1 : 1;
|
|
||||||
|
|
||||||
rows.sort((a, b) => {
|
|
||||||
const aValue = a.querySelector(`[data-${sortBy}]`).textContent;
|
|
||||||
const bValue = b.querySelector(`[data-${sortBy}]`).textContent;
|
|
||||||
return direction * aValue.localeCompare(bValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
const tbody = document.querySelector('#militantesTable tbody');
|
|
||||||
rows.forEach(row => tbody.appendChild(row));
|
|
||||||
|
|
||||||
headers.forEach(h => h.classList.remove('asc', 'desc'));
|
|
||||||
this.classList.add(direction === 1 ? 'asc' : 'desc');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Funções auxiliares
|
|
||||||
function updateCount() {
|
|
||||||
const visibleRows = document.querySelectorAll('#militantesTable tbody tr:not([style*="display: none"])');
|
|
||||||
document.getElementById('countMilitantes').textContent = visibleRows.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertToCSV(data) {
|
|
||||||
const headers = ['Nome', 'CPF', 'Email', 'Telefone', 'Célula'];
|
|
||||||
const rows = data.map(item => [
|
|
||||||
item.nome,
|
|
||||||
item.cpf,
|
|
||||||
item.email,
|
|
||||||
item.telefone,
|
|
||||||
item.celula
|
|
||||||
]);
|
|
||||||
|
|
||||||
return [headers, ...rows]
|
|
||||||
.map(row => row.map(cell => `"${cell}"`).join(','))
|
|
||||||
.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadCSV(csv, filename) {
|
|
||||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = URL.createObjectURL(blob);
|
|
||||||
link.download = filename;
|
|
||||||
link.click();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
=======
|
|
||||||
<script src="{{ url_for('static', filename='js/militantes.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/militantes.js') }}"></script>
|
||||||
>>>>>>> 4b99410 (fix: corrige funcionamento do modal de edição de militantes - Corrige bloco de scripts e melhora tratamento de dados)
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_css %}
|
{% block extra_css %}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form id="formNovoMilitante" method="post" action="{{ url_for('novo_militante') }}">
|
<form id="formNovoMilitante" method="post" action="{{ url_for('criar_militante') }}">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label for="nome" class="form-label">Nome:</label>
|
<label for="nome" class="form-label">Nome:</label>
|
||||||
|
|||||||
Reference in New Issue
Block a user