feat: Melhorias de segurança e interface - Segurança: Implementação de CSRF token em formulários, validação no backend, proteção AJAX - QR Code: Preservação do otp_secret, evita geração desnecessária - Interface: Correções visuais, padronização de cores, melhorias em formulários
This commit is contained in:
483
app.py
483
app.py
@@ -21,6 +21,7 @@ from functions.database import (
|
||||
EmailMilitante,
|
||||
init_database,
|
||||
EstadoMilitante,
|
||||
Endereco,
|
||||
)
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker, joinedload
|
||||
@@ -48,6 +49,7 @@ from flask_login import LoginManager, UserMixin, login_user, logout_user, login_
|
||||
import random
|
||||
import string
|
||||
from sqlalchemy.sql import func
|
||||
from flask_wtf.csrf import CSRFProtect
|
||||
|
||||
load_dotenv()
|
||||
|
||||
@@ -55,6 +57,9 @@ app = Flask(__name__)
|
||||
app.secret_key = os.getenv('SECRET_KEY', secrets.token_hex(16))
|
||||
bootstrap = Bootstrap5(app)
|
||||
|
||||
# Configurar CSRF Protection
|
||||
csrf = CSRFProtect(app)
|
||||
|
||||
# Configurar Flask-Login
|
||||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
@@ -310,79 +315,108 @@ def home():
|
||||
db.close()
|
||||
|
||||
# Rota para criar um novo militante
|
||||
@app.route('/militantes/criar', methods=['GET', 'POST'])
|
||||
@app.route("/militantes/criar", methods=["POST"])
|
||||
@require_login
|
||||
@require_permission('gerenciar_militantes')
|
||||
def criar_militante():
|
||||
if request.method == 'POST':
|
||||
"""Cria um novo militante"""
|
||||
db = get_db_connection()
|
||||
try:
|
||||
nome = request.form['nome']
|
||||
email = request.form['email']
|
||||
cpf = request.form['cpf']
|
||||
titulo_eleitoral = request.form['titulo_eleitoral']
|
||||
data_nascimento = datetime.strptime(request.form['data_nascimento'], '%Y-%m-%d').date() if request.form['data_nascimento'] else None
|
||||
data_entrada_oci = datetime.strptime(request.form['data_entrada_oci'], '%Y-%m-%d').date() if request.form['data_entrada_oci'] else None
|
||||
data_efetivacao_oci = datetime.strptime(request.form['data_efetivacao_oci'], '%Y-%m-%d').date() if request.form['data_efetivacao_oci'] else None
|
||||
telefone1 = request.form['telefone1']
|
||||
telefone2 = request.form['telefone2']
|
||||
profissao = request.form['profissao']
|
||||
regime_trabalho = request.form['regime_trabalho']
|
||||
empresa = request.form['empresa']
|
||||
contratante = request.form['contratante']
|
||||
instituicao_ensino = request.form['instituicao_ensino']
|
||||
tipo_instituicao = request.form['tipo_instituicao']
|
||||
sindicato = request.form['sindicato']
|
||||
cargo_sindical = request.form['cargo_sindical']
|
||||
dirigente_sindical = 'dirigente_sindical' in request.form
|
||||
central_sindical = request.form['central_sindical']
|
||||
setor_id = request.form['setor_id']
|
||||
celula_id = request.form['celula_id']
|
||||
# Validar CPF
|
||||
cpf = request.form.get('cpf')
|
||||
if not validar_cpf(cpf):
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': 'CPF inválido'
|
||||
}), 400
|
||||
|
||||
# Processar responsabilidades
|
||||
responsabilidades = 0
|
||||
if 'responsabilidades' in request.form:
|
||||
for responsabilidade in request.form.getlist('responsabilidades'):
|
||||
responsabilidades |= int(responsabilidade)
|
||||
# Verificar se já existe militante com este CPF
|
||||
militante_existente = db.query(Militante).filter(Militante.cpf == cpf).first()
|
||||
if militante_existente:
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': 'CPF já cadastrado'
|
||||
}), 400
|
||||
|
||||
militante = Militante(
|
||||
nome=nome,
|
||||
email=email,
|
||||
cpf=cpf,
|
||||
titulo_eleitoral=titulo_eleitoral,
|
||||
data_nascimento=data_nascimento,
|
||||
data_entrada_oci=data_entrada_oci,
|
||||
data_efetivacao_oci=data_efetivacao_oci,
|
||||
telefone1=telefone1,
|
||||
telefone2=telefone2,
|
||||
profissao=profissao,
|
||||
regime_trabalho=regime_trabalho,
|
||||
empresa=empresa,
|
||||
contratante=contratante,
|
||||
instituicao_ensino=instituicao_ensino,
|
||||
tipo_instituicao=tipo_instituicao,
|
||||
sindicato=sindicato,
|
||||
cargo_sindical=cargo_sindical,
|
||||
dirigente_sindical=dirigente_sindical,
|
||||
central_sindical=central_sindical,
|
||||
setor_id=setor_id,
|
||||
celula_id=celula_id,
|
||||
responsabilidades=responsabilidades
|
||||
# Criar endereço
|
||||
endereco = Endereco(
|
||||
cep=request.form.get('cep'),
|
||||
estado=request.form.get('estado'),
|
||||
cidade=request.form.get('cidade'),
|
||||
bairro=request.form.get('bairro'),
|
||||
logradouro=request.form.get('logradouro'),
|
||||
numero=request.form.get('numero'),
|
||||
complemento=request.form.get('complemento')
|
||||
)
|
||||
db.add(endereco)
|
||||
db.flush() # Gerar ID do endereço
|
||||
|
||||
db_session.add(militante)
|
||||
db_session.commit()
|
||||
# Criar militante
|
||||
militante = Militante(
|
||||
# Dados Básicos
|
||||
nome=request.form.get('nome'),
|
||||
cpf=cpf,
|
||||
titulo_eleitoral=request.form.get('titulo_eleitoral'),
|
||||
data_nascimento=datetime.strptime(request.form.get('data_nascimento'), '%Y-%m-%d') if request.form.get('data_nascimento') else None,
|
||||
data_entrada_oci=datetime.strptime(request.form.get('data_entrada_oci'), '%Y-%m-%d') if request.form.get('data_entrada_oci') else None,
|
||||
data_efetivacao_oci=datetime.strptime(request.form.get('data_efetivacao_oci'), '%Y-%m-%d') if request.form.get('data_efetivacao_oci') else None,
|
||||
|
||||
flash('Militante criado com sucesso!', 'success')
|
||||
return redirect(url_for('listar_militantes'))
|
||||
# Contato
|
||||
telefone1=request.form.get('telefone1'),
|
||||
telefone2=request.form.get('telefone2'),
|
||||
endereco_id=endereco.id,
|
||||
|
||||
# Profissional
|
||||
profissao=request.form.get('profissao'),
|
||||
regime_trabalho=request.form.get('regime_trabalho'),
|
||||
empresa=request.form.get('empresa'),
|
||||
contratante=request.form.get('contratante'),
|
||||
|
||||
# Acadêmico
|
||||
instituicao_ensino=request.form.get('instituicao_ensino'),
|
||||
tipo_instituicao=request.form.get('tipo_instituicao'),
|
||||
|
||||
# Sindical
|
||||
sindicato=request.form.get('sindicato'),
|
||||
cargo_sindical=request.form.get('cargo_sindical'),
|
||||
central_sindical=request.form.get('central_sindical'),
|
||||
dirigente_sindical=request.form.get('dirigente_sindical') == 'on',
|
||||
|
||||
# Organização
|
||||
estado=EstadoMilitante(request.form.get('estado', 'ATIVO')),
|
||||
celula_id=request.form.get('celula_id', type=int),
|
||||
responsabilidades=request.form.get('responsabilidades', type=int, default=0),
|
||||
|
||||
# Por padrão, todo novo militante é aspirante
|
||||
aspirante=True,
|
||||
data_inicio_aspirante=datetime.now()
|
||||
)
|
||||
db.add(militante)
|
||||
db.flush() # Gerar ID do militante
|
||||
|
||||
# Criar email principal
|
||||
email = EmailMilitante(
|
||||
email=request.form.get('email'),
|
||||
principal=True,
|
||||
militante_id=militante.id
|
||||
)
|
||||
db.add(email)
|
||||
|
||||
db.commit()
|
||||
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'message': 'Militante criado com sucesso!'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db_session.rollback()
|
||||
flash(f'Erro ao criar militante: {str(e)}', 'danger')
|
||||
return redirect(url_for('criar_militante'))
|
||||
|
||||
setores = Setor.query.all()
|
||||
celulas = Celula.query.all()
|
||||
return render_template('criar_militante.html', setores=setores, celulas=celulas)
|
||||
db.rollback()
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': f'Erro ao criar militante: {str(e)}'
|
||||
}), 500
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
# Rota para listar militantes
|
||||
@app.route("/militantes")
|
||||
@@ -904,88 +938,150 @@ def listar_relatorios_vendas():
|
||||
db.close()
|
||||
|
||||
# Rota para editar militante
|
||||
@app.route("/militantes/editar/<int:id>", methods=["GET", "POST"])
|
||||
@login_required
|
||||
@session_timeout
|
||||
def editar_militante(id):
|
||||
db = get_db_connection()
|
||||
try:
|
||||
militante = db.query(Militante).get(id)
|
||||
if not militante:
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return jsonify({'status': 'error', 'message': 'Militante não encontrado'}), 404
|
||||
flash('Militante não encontrado', 'danger')
|
||||
return redirect(url_for('listar_militantes'))
|
||||
@app.route("/militantes/editar/<int:militante_id>", methods=["POST"])
|
||||
@require_login
|
||||
@require_permission('gerenciar_militantes')
|
||||
def editar_militante(militante_id):
|
||||
"""Edita um militante existente"""
|
||||
print(f"Iniciando edição do militante {militante_id}")
|
||||
print(f"Dados recebidos: {request.form}")
|
||||
|
||||
if request.method == "POST":
|
||||
nome = request.form.get("nome")
|
||||
cpf = request.form.get("cpf")
|
||||
email = request.form.get("email")
|
||||
telefone = request.form.get("telefone")
|
||||
endereco = request.form.get("endereco")
|
||||
filiado = request.form.get("filiado") == "on"
|
||||
|
||||
# Validar CPF
|
||||
if not validar_cpf(cpf):
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return jsonify({'status': 'error', 'message': 'CPF inválido'}), 400
|
||||
flash('CPF inválido', 'danger')
|
||||
return render_template('editar_militante.html', militante=militante)
|
||||
|
||||
# Verificar se já existe outro militante com este CPF
|
||||
militante_existente = db.query(Militante).filter(
|
||||
Militante.cpf == cpf,
|
||||
Militante.id != id
|
||||
).first()
|
||||
if militante_existente:
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return jsonify({'status': 'error', 'message': 'CPF já cadastrado para outro militante'}), 400
|
||||
flash('CPF já cadastrado para outro militante', 'danger')
|
||||
return render_template('editar_militante.html', militante=militante)
|
||||
|
||||
db = get_db_connection()
|
||||
try:
|
||||
militante.nome = nome
|
||||
militante.cpf = cpf
|
||||
militante.email = email
|
||||
militante.telefone = telefone
|
||||
militante.endereco = endereco
|
||||
militante.filiado = filiado
|
||||
db.commit()
|
||||
militante = db.query(Militante).options(
|
||||
joinedload(Militante.endereco),
|
||||
joinedload(Militante.emails)
|
||||
).get(militante_id)
|
||||
|
||||
if not militante:
|
||||
print(f"Militante {militante_id} não encontrado")
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': 'Militante não encontrado'
|
||||
}), 404
|
||||
|
||||
print(f"Militante encontrado: {militante.nome}")
|
||||
|
||||
# Dados Básicos
|
||||
militante.nome = request.form.get('nome')
|
||||
militante.cpf = request.form.get('cpf')
|
||||
militante.titulo_eleitoral = request.form.get('titulo_eleitoral')
|
||||
militante.data_nascimento = datetime.strptime(request.form.get('data_nascimento'), '%Y-%m-%d') if request.form.get('data_nascimento') else None
|
||||
militante.data_entrada_oci = datetime.strptime(request.form.get('data_entrada_oci'), '%Y-%m-%d') if request.form.get('data_entrada_oci') else None
|
||||
militante.data_efetivacao_oci = datetime.strptime(request.form.get('data_efetivacao_oci'), '%Y-%m-%d') if request.form.get('data_efetivacao_oci') else None
|
||||
|
||||
print("Dados básicos atualizados")
|
||||
|
||||
# Contato
|
||||
militante.telefone1 = request.form.get('telefone1')
|
||||
militante.telefone2 = request.form.get('telefone2')
|
||||
|
||||
# Email (atualizar o principal)
|
||||
email = request.form.get('email')
|
||||
if email:
|
||||
if militante.emails:
|
||||
militante.emails[0].endereco_email = email
|
||||
else:
|
||||
novo_email = EmailMilitante(
|
||||
endereco_email=email,
|
||||
militante_id=militante.id,
|
||||
principal=True
|
||||
)
|
||||
db.add(novo_email)
|
||||
|
||||
print("Dados de contato atualizados")
|
||||
|
||||
# Endereço
|
||||
if not militante.endereco:
|
||||
militante.endereco = Endereco()
|
||||
db.add(militante.endereco)
|
||||
|
||||
militante.endereco.cep = request.form.get('cep')
|
||||
militante.endereco.estado = request.form.get('estado')
|
||||
militante.endereco.cidade = request.form.get('cidade')
|
||||
militante.endereco.bairro = request.form.get('bairro')
|
||||
militante.endereco.rua = request.form.get('rua')
|
||||
militante.endereco.numero = request.form.get('numero')
|
||||
militante.endereco.complemento = request.form.get('complemento')
|
||||
|
||||
print("Dados de endereço atualizados")
|
||||
|
||||
# Profissional
|
||||
militante.profissao = request.form.get('profissao')
|
||||
militante.regime_trabalho = request.form.get('regime_trabalho')
|
||||
militante.empresa = request.form.get('empresa')
|
||||
militante.contratante = request.form.get('contratante')
|
||||
|
||||
# Acadêmico
|
||||
militante.instituicao_ensino = request.form.get('instituicao_ensino')
|
||||
militante.tipo_instituicao = request.form.get('tipo_instituicao')
|
||||
|
||||
# Sindical
|
||||
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') == 'on'
|
||||
|
||||
print("Dados profissionais e sindicais atualizados")
|
||||
|
||||
# Organização
|
||||
estado_str = request.form.get('estado', 'ATIVO').lower()
|
||||
print(f"Estado recebido: {estado_str}")
|
||||
|
||||
# Converter o estado para o enum
|
||||
try:
|
||||
militante.estado = EstadoMilitante[estado_str.upper()]
|
||||
print(f"Estado convertido: {militante.estado}")
|
||||
except (KeyError, ValueError) as e:
|
||||
print(f"Erro ao converter estado: {str(e)}")
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': f'Estado inválido: {estado_str}'
|
||||
}), 400
|
||||
|
||||
# Tratar celula_id corretamente
|
||||
celula_id = request.form.get('celula_id')
|
||||
if celula_id:
|
||||
try:
|
||||
militante.celula_id = int(celula_id)
|
||||
except (ValueError, TypeError):
|
||||
militante.celula_id = None
|
||||
else:
|
||||
militante.celula_id = None
|
||||
|
||||
# Tratar responsabilidades corretamente
|
||||
responsabilidades = request.form.getlist('responsabilidades')
|
||||
if responsabilidades:
|
||||
try:
|
||||
militante.responsabilidades = sum(int(r) for r in responsabilidades)
|
||||
except (ValueError, TypeError):
|
||||
militante.responsabilidades = 0
|
||||
else:
|
||||
militante.responsabilidades = 0
|
||||
|
||||
print("Dados organizacionais atualizados")
|
||||
|
||||
# Se o estado mudou para DESLIGADO, registrar data e motivo
|
||||
if militante.estado == EstadoMilitante.DESLIGADO:
|
||||
militante.data_desligamento = datetime.now()
|
||||
militante.motivo_desligamento = request.form.get('motivo_desligamento')
|
||||
|
||||
db.commit()
|
||||
print("Alterações salvas com sucesso")
|
||||
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'message': 'Militante atualizado com sucesso',
|
||||
'militante': {
|
||||
'id': militante.id,
|
||||
'nome': militante.nome,
|
||||
'cpf': militante.cpf,
|
||||
'email': militante.email,
|
||||
'telefone': militante.telefone,
|
||||
'endereco': militante.endereco,
|
||||
'filiado': militante.filiado
|
||||
}
|
||||
'message': f'Militante {militante.nome} atualizado com sucesso!'
|
||||
})
|
||||
|
||||
flash('Militante atualizado com sucesso!', 'success')
|
||||
return redirect(url_for('listar_militantes'))
|
||||
|
||||
except Exception as e:
|
||||
print(f"Erro ao atualizar militante: {str(e)}")
|
||||
db.rollback()
|
||||
print(f"Erro ao atualizar militante: {e}")
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return jsonify({'status': 'error', 'message': 'Erro ao atualizar militante'}), 500
|
||||
flash('Erro ao atualizar militante', 'danger')
|
||||
return render_template('editar_militante.html', militante=militante)
|
||||
|
||||
return render_template('editar_militante.html', militante=militante)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Erro ao editar militante: {e}")
|
||||
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return jsonify({'status': 'error', 'message': 'Erro ao carregar militante'}), 500
|
||||
flash('Erro ao carregar militante', 'danger')
|
||||
return redirect(url_for('listar_militantes'))
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': f'Erro ao atualizar militante: {str(e)}'
|
||||
}), 500
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@@ -1459,6 +1555,98 @@ def excluir_assinatura(id):
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@app.route("/militantes/<int:militante_id>/dados")
|
||||
@require_login
|
||||
@require_permission('gerenciar_militantes')
|
||||
def get_militante_dados(militante_id):
|
||||
"""Retorna os dados completos de um militante"""
|
||||
print(f"Buscando dados do militante {militante_id}")
|
||||
db = get_db_connection()
|
||||
try:
|
||||
militante = db.query(Militante).options(
|
||||
joinedload(Militante.endereco),
|
||||
joinedload(Militante.emails),
|
||||
joinedload(Militante.celula)
|
||||
).get(militante_id)
|
||||
|
||||
if not militante:
|
||||
print(f"Militante {militante_id} não encontrado")
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': 'Militante não encontrado'
|
||||
}), 404
|
||||
|
||||
print(f"Militante {militante_id} encontrado: {militante.nome}")
|
||||
|
||||
# Formatar datas para o formato YYYY-MM-DD
|
||||
data_nascimento = militante.data_nascimento.strftime('%Y-%m-%d') if militante.data_nascimento else None
|
||||
data_entrada = militante.data_entrada_oci.strftime('%Y-%m-%d') if militante.data_entrada_oci else None
|
||||
data_efetivacao = militante.data_efetivacao_oci.strftime('%Y-%m-%d') if militante.data_efetivacao_oci else None
|
||||
|
||||
# Buscar o primeiro email do militante
|
||||
email_principal = None
|
||||
if militante.emails and len(militante.emails) > 0:
|
||||
email_principal = militante.emails[0].endereco_email
|
||||
|
||||
dados = {
|
||||
'id': militante.id,
|
||||
'nome': militante.nome,
|
||||
'cpf': militante.cpf,
|
||||
'titulo_eleitoral': militante.titulo_eleitoral,
|
||||
'data_nascimento': data_nascimento,
|
||||
'data_entrada_oci': data_entrada,
|
||||
'data_efetivacao_oci': data_efetivacao,
|
||||
|
||||
# Contato
|
||||
'telefone1': militante.telefone1,
|
||||
'telefone2': militante.telefone2,
|
||||
'email': email_principal,
|
||||
|
||||
# Endereço
|
||||
'endereco': {
|
||||
'cep': militante.endereco.cep if militante.endereco else None,
|
||||
'estado': militante.endereco.estado if militante.endereco else None,
|
||||
'cidade': militante.endereco.cidade if militante.endereco else None,
|
||||
'bairro': militante.endereco.bairro 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
|
||||
} if militante.endereco else None,
|
||||
|
||||
# Profissional
|
||||
'profissao': militante.profissao,
|
||||
'regime_trabalho': militante.regime_trabalho,
|
||||
'empresa': militante.empresa,
|
||||
'contratante': militante.contratante,
|
||||
|
||||
# Acadêmico
|
||||
'instituicao_ensino': militante.instituicao_ensino,
|
||||
'tipo_instituicao': militante.tipo_instituicao,
|
||||
|
||||
# Sindical
|
||||
'sindicato': militante.sindicato,
|
||||
'cargo_sindical': militante.cargo_sindical,
|
||||
'central_sindical': militante.central_sindical,
|
||||
'dirigente_sindical': militante.dirigente_sindical,
|
||||
|
||||
# Organização
|
||||
'estado': militante.estado.value if militante.estado else 'ATIVO',
|
||||
'celula_id': militante.celula_id,
|
||||
'responsabilidades': militante.responsabilidades
|
||||
}
|
||||
|
||||
print(f"Dados formatados: {dados}")
|
||||
return jsonify(dados)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Erro ao buscar dados do militante {militante_id}: {str(e)}")
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': f'Erro ao buscar dados do militante: {str(e)}'
|
||||
}), 500
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
# ... existing code ...
|
||||
@@ -1474,13 +1662,6 @@ def init_system():
|
||||
print("Inicializando banco de dados...")
|
||||
init_database()
|
||||
|
||||
# Criar admin
|
||||
create_admin_user()
|
||||
|
||||
# Criar usuários de teste
|
||||
create_test_users()
|
||||
|
||||
# Verificar configuração
|
||||
db = get_db_connection()
|
||||
try:
|
||||
# Verificar admin
|
||||
@@ -1489,10 +1670,15 @@ def init_system():
|
||||
print("\nAdmin configurado:")
|
||||
print(f"Username: admin")
|
||||
print(f"Senha: admin123")
|
||||
if os.path.exists('admin_qr.png'):
|
||||
print("OTP: Usando configuração existente do arquivo admin_qr.png")
|
||||
if admin.otp_secret:
|
||||
print("OTP: Usando configuração existente")
|
||||
else:
|
||||
admin.otp_secret = pyotp.random_base32()
|
||||
db.commit()
|
||||
print("OTP: Nova configuração gerada")
|
||||
else:
|
||||
# Criar admin se não existir
|
||||
create_admin_user()
|
||||
|
||||
# Verificar usuários de teste
|
||||
test_users = ['aligner', 'tester', 'deployer']
|
||||
@@ -1503,14 +1689,19 @@ def init_system():
|
||||
print(f"Username: {username}")
|
||||
print(f"Senha: Test123!@#")
|
||||
if user.otp_secret:
|
||||
print("OTP: Configurado")
|
||||
print("OTP: Usando configuração existente")
|
||||
else:
|
||||
print("OTP: Não configurado")
|
||||
user.otp_secret = pyotp.random_base32()
|
||||
db.commit()
|
||||
print("OTP: Nova configuração gerada")
|
||||
else:
|
||||
# Criar usuário de teste se não existir
|
||||
create_test_users()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
print("\nInstruções:")
|
||||
print("1. Configure o OTP usando o QR code gerado")
|
||||
print("1. Configure o OTP usando o QR code gerado (apenas para novos usuários)")
|
||||
print("2. Faça login com as credenciais fornecidas")
|
||||
print("3. Altere a senha no primeiro login")
|
||||
|
||||
|
||||
@@ -55,6 +55,10 @@ def create_admin_user():
|
||||
|
||||
if admin:
|
||||
print("\n=== Usuário Admin Encontrado ===")
|
||||
if not admin.otp_secret:
|
||||
print("Gerando novo segredo OTP...")
|
||||
admin.generate_otp_secret()
|
||||
db.commit()
|
||||
else:
|
||||
print("\n=== Criando Novo Usuário Admin ===")
|
||||
# Criar novo usuário admin
|
||||
@@ -70,8 +74,16 @@ def create_admin_user():
|
||||
db.add(admin)
|
||||
db.commit()
|
||||
|
||||
# Gerar QR code uma única vez
|
||||
# Gerar QR code apenas se solicitado ou se for novo usuário
|
||||
if not os.path.exists('admin_qr.png'):
|
||||
qr_path, otp_uri = generate_qr_code(admin)
|
||||
print("\n=== QR Code Gerado ===")
|
||||
print(f"QR Code salvo em: {qr_path}")
|
||||
print(f"URI do OTP: {otp_uri}")
|
||||
else:
|
||||
print("\n=== QR Code Existente ===")
|
||||
print("Usando QR Code existente em: admin_qr.png")
|
||||
qr_path = 'admin_qr.png'
|
||||
|
||||
# Mostrar informações
|
||||
print("\n=== Informações do Admin ===")
|
||||
@@ -80,10 +92,6 @@ def create_admin_user():
|
||||
print(f"Senha: admin123")
|
||||
print(f"Segredo OTP: {admin.otp_secret}")
|
||||
|
||||
print("\n=== QR Code Gerado ===")
|
||||
print(f"QR Code salvo em: {qr_path}")
|
||||
print(f"URI do OTP: {otp_uri}")
|
||||
|
||||
# Gerar código atual para verificação
|
||||
totp = pyotp.TOTP(admin.otp_secret)
|
||||
current_code = totp.now()
|
||||
|
||||
@@ -56,17 +56,15 @@ def create_test_users():
|
||||
)
|
||||
user.set_password(user_data['password'])
|
||||
user.tipo = "ADMIN" if user_data['is_admin'] else "USUARIO"
|
||||
db.add(user)
|
||||
db.commit()
|
||||
|
||||
# Se for o usuário teste, usar o mesmo OTP do admin
|
||||
if user_data['username'] == 'teste' and admin_otp_secret:
|
||||
user.otp_secret = admin_otp_secret
|
||||
db.commit()
|
||||
else:
|
||||
# Gerar novo OTP para outros usuários
|
||||
otp_secret = pyotp.random_base32()
|
||||
user.otp_secret = otp_secret
|
||||
user.otp_secret = pyotp.random_base32()
|
||||
|
||||
db.add(user)
|
||||
db.commit()
|
||||
|
||||
# Atribuir role de Secretário Geral para o usuário teste
|
||||
@@ -77,6 +75,17 @@ def create_test_users():
|
||||
db.commit()
|
||||
|
||||
print(f"Usuário {user_data['username']} criado com sucesso!")
|
||||
|
||||
# Gerar QR code para o novo usuário
|
||||
qr_path = f"{user_data['username']}_qr.png"
|
||||
if not os.path.exists(qr_path):
|
||||
totp = pyotp.TOTP(user.otp_secret)
|
||||
qr = qrcode.QRCode(version=1, box_size=10, border=5)
|
||||
qr.add_data(totp.provisioning_uri(user.email, issuer_name="Sistema de Controles"))
|
||||
qr.make(fit=True)
|
||||
img = qr.make_image(fill_color="black", back_color="white")
|
||||
img.save(qr_path)
|
||||
print(f"QR Code gerado para {user_data['username']} em: {qr_path}")
|
||||
else:
|
||||
print(f"Usuário {user_data['username']} já existe")
|
||||
|
||||
@@ -85,6 +94,22 @@ def create_test_users():
|
||||
user.otp_secret = admin_otp_secret
|
||||
db.commit()
|
||||
print(f"OTP do usuário teste atualizado para o mesmo do admin")
|
||||
elif not user.otp_secret:
|
||||
# Se não tiver OTP, gerar um novo
|
||||
user.otp_secret = pyotp.random_base32()
|
||||
db.commit()
|
||||
print(f"Novo OTP gerado para {user_data['username']}")
|
||||
|
||||
# Gerar QR code
|
||||
qr_path = f"{user_data['username']}_qr.png"
|
||||
if not os.path.exists(qr_path):
|
||||
totp = pyotp.TOTP(user.otp_secret)
|
||||
qr = qrcode.QRCode(version=1, box_size=10, border=5)
|
||||
qr.add_data(totp.provisioning_uri(user.email, issuer_name="Sistema de Controles"))
|
||||
qr.make(fit=True)
|
||||
img = qr.make_image(fill_color="black", back_color="white")
|
||||
img.save(qr_path)
|
||||
print(f"QR Code gerado para {user_data['username']} em: {qr_path}")
|
||||
|
||||
# Verificar se o usuário teste tem a role de Secretário Geral
|
||||
if user_data['username'] == 'teste':
|
||||
|
||||
@@ -7,6 +7,13 @@
|
||||
--green: #198754;
|
||||
--cyan: #0dcaf0;
|
||||
--yellow: #ffc107;
|
||||
--primary-color: #dc3545;
|
||||
--primary-hover: #bb2d3b;
|
||||
--text-color: #333;
|
||||
--text-muted: #6c757d;
|
||||
--bg-hover: #f8f9fa;
|
||||
--tab-active-color: var(--primary-color);
|
||||
--tab-hover-color: rgba(220, 53, 69, 0.1);
|
||||
}
|
||||
|
||||
/* Tabelas */
|
||||
@@ -242,3 +249,170 @@
|
||||
font-size: 1.25rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* Tabs */
|
||||
.nav-tabs {
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link,
|
||||
.nav-tabs .nav-link:focus,
|
||||
.nav-tabs .nav-link:hover,
|
||||
.nav-tabs .nav-link.active {
|
||||
color: var(--primary-color) !important;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
padding: 0.75rem 1.5rem;
|
||||
margin-bottom: -2px;
|
||||
transition: all 0.2s ease-in-out;
|
||||
font-weight: 500;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link:hover {
|
||||
background-color: var(--tab-hover-color);
|
||||
border-bottom: 2px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link.active {
|
||||
font-weight: 600;
|
||||
background-color: var(--tab-hover-color);
|
||||
border-bottom: 2px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link i {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.tab-pane {
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsividade das abas */
|
||||
@media (max-width: 768px) {
|
||||
.nav-tabs {
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link {
|
||||
white-space: nowrap;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Estilo para botões com largura fixa */
|
||||
.btn-fixed-width {
|
||||
min-width: 120px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.375rem 0.75rem;
|
||||
text-align: center;
|
||||
height: 38px;
|
||||
line-height: 1.5;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.btn-fixed-width i {
|
||||
margin-right: 8px;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
/* Estilo para o backdrop com blur em todos os modais */
|
||||
.modal-backdrop.show {
|
||||
backdrop-filter: blur(8px);
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
/* Estilo para o botão de fechar dos modais */
|
||||
.btn-close {
|
||||
background-color: transparent;
|
||||
padding: 0.5rem;
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.2s;
|
||||
filter: invert(1) grayscale(100%) brightness(200%);
|
||||
}
|
||||
|
||||
.btn-close:hover {
|
||||
opacity: 1;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* Estilos do Modal */
|
||||
.modal-header {
|
||||
background-color: #343a40;
|
||||
color: #fff;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-header i {
|
||||
color: #fff;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.modal-header .btn-close {
|
||||
filter: invert(1) grayscale(100%) brightness(200%);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.modal-header .btn-close:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Estilos globais de formulário */
|
||||
.form-control:focus,
|
||||
.form-select:focus,
|
||||
.form-check-input:focus,
|
||||
.btn:focus,
|
||||
.btn-check:focus + .btn {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
|
||||
}
|
||||
|
||||
.form-control:hover,
|
||||
.form-select:hover {
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Input group com foco */
|
||||
.input-group .form-control:focus,
|
||||
.input-group .form-select:focus {
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Checkbox e radio */
|
||||
.form-check-input:checked {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Date picker */
|
||||
input[type="date"]:focus {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
|
||||
}
|
||||
@@ -61,6 +61,8 @@ function filtrarMilitantes() {
|
||||
|
||||
// Configurar eventos quando o DOM estiver carregado
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('DOM carregado, configurando eventos...');
|
||||
|
||||
// Configurar pesquisa
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
if (searchInput) {
|
||||
@@ -109,7 +111,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
});
|
||||
|
||||
console.log('DOM carregado');
|
||||
console.log('Configurando modal de edição...');
|
||||
|
||||
// Configuração do modal de edição
|
||||
const modalEditarMilitante = document.getElementById('modalEditarMilitante');
|
||||
@@ -118,6 +120,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (modalEditarMilitante) {
|
||||
console.log('Modal encontrado, configurando eventos...');
|
||||
|
||||
// Criar instância do modal Bootstrap
|
||||
const modalInstance = new bootstrap.Modal(modalEditarMilitante);
|
||||
console.log('Instância do modal criada:', modalInstance);
|
||||
|
||||
modalEditarMilitante.addEventListener('show.bs.modal', function(event) {
|
||||
console.log('Modal sendo exibido');
|
||||
const button = event.relatedTarget;
|
||||
@@ -129,33 +135,155 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
|
||||
const militanteId = button.getAttribute('data-militante-id');
|
||||
const militanteNome = button.getAttribute('data-militante-nome');
|
||||
console.log('ID do militante:', militanteId);
|
||||
console.log('Nome do militante:', militanteNome);
|
||||
|
||||
// Dados do militante
|
||||
const dados = {
|
||||
nome: button.getAttribute('data-militante-nome'),
|
||||
cpf: button.getAttribute('data-militante-cpf'),
|
||||
email: button.getAttribute('data-militante-email'),
|
||||
telefone: button.getAttribute('data-militante-telefone'),
|
||||
endereco: button.getAttribute('data-militante-endereco'),
|
||||
filiado: button.getAttribute('data-militante-filiado')
|
||||
};
|
||||
console.log('Dados do militante:', dados);
|
||||
if (!militanteId) {
|
||||
console.error('ID do militante não encontrado no botão!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Preencher campos
|
||||
document.getElementById('editNome').value = dados.nome || '';
|
||||
document.getElementById('editCpf').value = dados.cpf || '';
|
||||
document.getElementById('editEmail').value = dados.email || '';
|
||||
document.getElementById('editTelefone').value = dados.telefone || '';
|
||||
document.getElementById('editEndereco').value = dados.endereco || '';
|
||||
document.getElementById('editFiliado').checked = dados.filiado === 'True';
|
||||
// Atualizar o título do modal com o nome do militante
|
||||
const modalTitle = modalEditarMilitante.querySelector('.modal-title');
|
||||
if (modalTitle) {
|
||||
modalTitle.innerHTML = `<i class="fas fa-user-edit me-2"></i>Editar ${militanteNome}`;
|
||||
}
|
||||
|
||||
// Configurar formulário
|
||||
const form = document.getElementById('formEditarMilitante');
|
||||
if (form) {
|
||||
form.action = `/militantes/editar/${militanteId}`;
|
||||
console.log('Action do formulário:', form.action);
|
||||
const idInput = document.getElementById('edit_militante_id');
|
||||
if (idInput) {
|
||||
idInput.value = militanteId;
|
||||
}
|
||||
console.log('Action do formulário:', form.action);
|
||||
console.log('ID do militante no formulário:', militanteId);
|
||||
} else {
|
||||
console.error('Formulário não encontrado!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Mostrar loading
|
||||
const modalBody = modalEditarMilitante.querySelector('.modal-body');
|
||||
if (modalBody) {
|
||||
modalBody.style.opacity = '0.5';
|
||||
}
|
||||
|
||||
// Buscar dados completos do militante via AJAX
|
||||
console.log(`Fazendo requisição para /militantes/${militanteId}/dados`);
|
||||
|
||||
// Obter o CSRF token
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||
console.log('CSRF token:', csrfToken);
|
||||
|
||||
fetch(`/militantes/${militanteId}/dados`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
.then(response => {
|
||||
console.log('Resposta recebida:', response);
|
||||
if (!response.ok) {
|
||||
return response.json().then(data => {
|
||||
throw new Error(data.message || `HTTP error! status: ${response.status}`);
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(dados => {
|
||||
console.log('Dados do militante recebidos:', dados);
|
||||
|
||||
if (!dados) {
|
||||
throw new Error('Dados do militante não encontrados');
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Preenchendo dados básicos...');
|
||||
// Dados Básicos
|
||||
document.getElementById('edit_nome').value = dados.nome || '';
|
||||
document.getElementById('edit_cpf').value = dados.cpf || '';
|
||||
document.getElementById('edit_titulo_eleitoral').value = dados.titulo_eleitoral || '';
|
||||
document.getElementById('edit_data_nascimento').value = dados.data_nascimento || '';
|
||||
document.getElementById('edit_data_entrada').value = dados.data_entrada_oci || '';
|
||||
document.getElementById('edit_data_efetivacao').value = dados.data_efetivacao_oci || '';
|
||||
console.log('Dados básicos preenchidos');
|
||||
|
||||
console.log('Preenchendo dados de contato...');
|
||||
// Contato
|
||||
document.getElementById('edit_telefone1').value = dados.telefone1 || '';
|
||||
document.getElementById('edit_telefone2').value = dados.telefone2 || '';
|
||||
document.getElementById('edit_email').value = dados.email || '';
|
||||
console.log('Dados de contato preenchidos');
|
||||
|
||||
console.log('Preenchendo dados de endereço...');
|
||||
// Endereço
|
||||
if (dados.endereco) {
|
||||
document.getElementById('edit_cep').value = dados.endereco.cep || '';
|
||||
document.getElementById('edit_estado').value = dados.endereco.estado || '';
|
||||
document.getElementById('edit_cidade').value = dados.endereco.cidade || '';
|
||||
document.getElementById('edit_bairro').value = dados.endereco.bairro || '';
|
||||
document.getElementById('edit_logradouro').value = dados.endereco.rua || '';
|
||||
document.getElementById('edit_numero').value = dados.endereco.numero || '';
|
||||
document.getElementById('edit_complemento').value = dados.endereco.complemento || '';
|
||||
}
|
||||
console.log('Dados de endereço preenchidos');
|
||||
|
||||
console.log('Preenchendo dados profissionais...');
|
||||
// Profissional
|
||||
document.getElementById('edit_profissao').value = dados.profissao || '';
|
||||
document.getElementById('edit_regime_trabalho').value = dados.regime_trabalho || '';
|
||||
document.getElementById('edit_empresa').value = dados.empresa || '';
|
||||
document.getElementById('edit_contratante').value = dados.contratante || '';
|
||||
console.log('Dados profissionais preenchidos');
|
||||
|
||||
console.log('Preenchendo dados acadêmicos...');
|
||||
// Acadêmico
|
||||
document.getElementById('edit_instituicao_ensino').value = dados.instituicao_ensino || '';
|
||||
document.getElementById('edit_tipo_instituicao').value = dados.tipo_instituicao || '';
|
||||
console.log('Dados acadêmicos preenchidos');
|
||||
|
||||
console.log('Preenchendo dados sindicais...');
|
||||
// Sindical
|
||||
document.getElementById('edit_sindicato').value = dados.sindicato || '';
|
||||
document.getElementById('edit_cargo_sindical').value = dados.cargo_sindical || '';
|
||||
document.getElementById('edit_central_sindical').value = dados.central_sindical || '';
|
||||
document.getElementById('edit_dirigente_sindical').checked = dados.dirigente_sindical || false;
|
||||
console.log('Dados sindicais preenchidos');
|
||||
|
||||
console.log('Preenchendo dados organizacionais...');
|
||||
// Organização
|
||||
document.getElementById('edit_estado_militante').value = dados.estado || 'ATIVO';
|
||||
document.getElementById('edit_celula').value = dados.celula_id || '';
|
||||
|
||||
// Responsabilidades
|
||||
const responsabilidades = dados.responsabilidades || 0;
|
||||
document.querySelectorAll('input[name="responsabilidades"]').forEach(checkbox => {
|
||||
const valor = parseInt(checkbox.value);
|
||||
checkbox.checked = (responsabilidades & valor) !== 0;
|
||||
});
|
||||
console.log('Dados organizacionais preenchidos');
|
||||
|
||||
console.log('Todos os dados preenchidos com sucesso!');
|
||||
} catch (error) {
|
||||
console.error('Erro ao preencher os campos:', error);
|
||||
throw new Error('Erro ao preencher os dados do militante: ' + error.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Erro ao buscar dados do militante:', error);
|
||||
alert('Erro ao carregar dados do militante: ' + error.message);
|
||||
})
|
||||
.finally(() => {
|
||||
if (modalBody) {
|
||||
modalBody.style.opacity = '1';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Envio do formulário de edição via AJAX
|
||||
@@ -163,53 +291,76 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (formEditarMilitante) {
|
||||
formEditarMilitante.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
console.log('Enviando formulário de edição...');
|
||||
|
||||
const formData = new FormData(this);
|
||||
console.log('Dados do formulário:', Object.fromEntries(formData));
|
||||
|
||||
fetch(this.action, {
|
||||
// Adicionar responsabilidades selecionadas
|
||||
const responsabilidades = Array.from(this.querySelectorAll('input[name="responsabilidades"]:checked'))
|
||||
.map(cb => parseInt(cb.value))
|
||||
.reduce((a, b) => a + b, 0);
|
||||
formData.set('responsabilidades', responsabilidades);
|
||||
|
||||
// Obter o CSRF token
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||
console.log('CSRF token:', csrfToken);
|
||||
|
||||
// Obter o ID do militante do campo hidden
|
||||
const militanteId = document.getElementById('edit_militante_id').value;
|
||||
console.log('ID do militante:', militanteId);
|
||||
|
||||
if (!militanteId) {
|
||||
console.error('ID do militante não encontrado!');
|
||||
mostrarAlerta('Erro: ID do militante não encontrado', 'danger');
|
||||
return;
|
||||
}
|
||||
|
||||
// Garantir que o campo de endereço está correto
|
||||
const logradouro = formData.get('logradouro');
|
||||
if (logradouro) {
|
||||
formData.set('rua', logradouro);
|
||||
formData.delete('logradouro');
|
||||
}
|
||||
|
||||
fetch(`/militantes/editar/${militanteId}`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
.then(response => {
|
||||
console.log('Resposta recebida:', response);
|
||||
if (!response.ok) {
|
||||
return response.json().then(data => {
|
||||
throw new Error(data.message || `HTTP error! status: ${response.status}`);
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Resposta processada:', data);
|
||||
if (data.status === 'success') {
|
||||
// Fechar o modal
|
||||
bootstrap.Modal.getInstance(modalEditarMilitante).hide();
|
||||
|
||||
// Atualizar a lista
|
||||
location.reload();
|
||||
|
||||
// Mostrar mensagem de sucesso
|
||||
const alertDiv = document.createElement('div');
|
||||
alertDiv.className = 'alert alert-success alert-dismissible fade show';
|
||||
alertDiv.innerHTML = `
|
||||
${data.message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
document.querySelector('.container').insertBefore(alertDiv, document.querySelector('.container').firstChild);
|
||||
mostrarAlerta(data.message, 'success');
|
||||
|
||||
// Recarregar a página após um breve delay
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 1500);
|
||||
} else {
|
||||
// Mostrar erro
|
||||
const alertDiv = document.createElement('div');
|
||||
alertDiv.className = 'alert alert-danger alert-dismissible fade show';
|
||||
alertDiv.innerHTML = `
|
||||
${data.message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
document.querySelector('.modal-body').insertBefore(alertDiv, formEditarMilitante);
|
||||
throw new Error(data.message || 'Erro ao salvar dados');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Erro:', error);
|
||||
const alertDiv = document.createElement('div');
|
||||
alertDiv.className = 'alert alert-danger alert-dismissible fade show';
|
||||
alertDiv.innerHTML = `
|
||||
Erro ao atualizar militante. Tente novamente.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
document.querySelector('.modal-body').insertBefore(alertDiv, formEditarMilitante);
|
||||
console.error('Erro ao enviar formulário:', error);
|
||||
mostrarAlerta(`Erro ao salvar dados: ${error.message}`, 'danger');
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -334,32 +485,31 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
|
||||
// Máscara para CPF
|
||||
const cpfInput = document.getElementById('cpf');
|
||||
if (cpfInput) {
|
||||
cpfInput.addEventListener('input', function(e) {
|
||||
const cpfInputs = document.querySelectorAll('input[name="cpf"]');
|
||||
cpfInputs.forEach(input => {
|
||||
input.addEventListener('input', function(e) {
|
||||
let value = e.target.value.replace(/\D/g, '');
|
||||
if (value.length <= 11) {
|
||||
value = value.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3-$4');
|
||||
value = value.replace(/(\d{3})(\d)/, '$1.$2');
|
||||
value = value.replace(/(\d{3})(\d)/, '$1.$2');
|
||||
value = value.replace(/(\d{3})(\d{1,2})$/, '$1-$2');
|
||||
e.target.value = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Máscara para telefone
|
||||
const telefoneInput = document.getElementById('telefone');
|
||||
if (telefoneInput) {
|
||||
telefoneInput.addEventListener('input', function(e) {
|
||||
const telefoneInputs = document.querySelectorAll('input[name="telefone1"], input[name="telefone2"]');
|
||||
telefoneInputs.forEach(input => {
|
||||
input.addEventListener('input', function(e) {
|
||||
let value = e.target.value.replace(/\D/g, '');
|
||||
if (value.length <= 11) {
|
||||
if (value.length === 11) {
|
||||
value = value.replace(/(\d{2})(\d{5})(\d{4})/, '($1) $2-$3');
|
||||
} else {
|
||||
value = value.replace(/(\d{2})(\d{4})(\d{4})/, '($1) $2-$3');
|
||||
}
|
||||
value = value.replace(/(\d{2})(\d)/, '($1) $2');
|
||||
value = value.replace(/(\d{4,5})(\d{4})$/, '$1-$2');
|
||||
e.target.value = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Limpar formulário e alertas quando o modal for fechado
|
||||
const modalNovoMilitante = document.getElementById('modalNovoMilitante');
|
||||
@@ -444,4 +594,71 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
document.body.removeChild(link);
|
||||
});
|
||||
}
|
||||
|
||||
// Configurar máscaras de input
|
||||
// CEP
|
||||
const cepInputs = document.querySelectorAll('input[name="cep"]');
|
||||
cepInputs.forEach(input => {
|
||||
input.addEventListener('input', function(e) {
|
||||
let value = e.target.value.replace(/\D/g, '');
|
||||
if (value.length <= 8) {
|
||||
value = value.replace(/(\d{5})(\d)/, '$1-$2');
|
||||
e.target.value = value;
|
||||
}
|
||||
});
|
||||
|
||||
// Buscar endereço pelo CEP
|
||||
input.addEventListener('blur', function(e) {
|
||||
const cep = e.target.value.replace(/\D/g, '');
|
||||
if (cep.length === 8) {
|
||||
fetch(`https://viacep.com.br/ws/${cep}/json/`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (!data.erro) {
|
||||
const form = input.closest('form');
|
||||
form.querySelector('input[name="logradouro"]').value = data.logradouro;
|
||||
form.querySelector('input[name="bairro"]').value = data.bairro;
|
||||
form.querySelector('input[name="cidade"]').value = data.localidade;
|
||||
form.querySelector('select[name="estado"]').value = data.uf;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Título Eleitoral
|
||||
const tituloInputs = document.querySelectorAll('input[name="titulo_eleitoral"]');
|
||||
tituloInputs.forEach(input => {
|
||||
input.addEventListener('input', function(e) {
|
||||
let value = e.target.value.replace(/\D/g, '');
|
||||
if (value.length <= 12) {
|
||||
value = value.replace(/(\d{4})(\d)/, '$1 $2');
|
||||
value = value.replace(/(\d{4})(\d)/, '$1 $2');
|
||||
e.target.value = value;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Função para mostrar alertas
|
||||
function mostrarAlerta(mensagem, tipo) {
|
||||
// Criar o elemento de alerta
|
||||
const alertDiv = document.createElement('div');
|
||||
alertDiv.className = `alert alert-${tipo} alert-dismissible fade show position-fixed top-0 start-50 translate-middle-x mt-3`;
|
||||
alertDiv.style.zIndex = '9999';
|
||||
alertDiv.innerHTML = `
|
||||
${mensagem}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
|
||||
// Adicionar o alerta ao corpo do documento
|
||||
document.body.appendChild(alertDiv);
|
||||
|
||||
// Configurar o Bootstrap alert
|
||||
const bsAlert = new bootstrap.Alert(alertDiv);
|
||||
|
||||
// Remover o alerta após 3 segundos
|
||||
setTimeout(() => {
|
||||
bsAlert.close();
|
||||
}, 3000);
|
||||
}
|
||||
@@ -3,12 +3,15 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>{% block title %}{% endblock %} - Controles OCI</title>
|
||||
|
||||
<!-- Bootstrap 5 CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css?v=1" rel="stylesheet">
|
||||
<!-- Font Awesome 6 -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css?v=1">
|
||||
<!-- Componentes CSS -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/components.css') }}">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
{% endwith %}
|
||||
|
||||
<form method="POST" action="{{ url_for('login') }}" class="needs-validation" novalidate>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<input type="text" class="form-control" id="email" name="email" placeholder="Email ou Usuário" required>
|
||||
<label for="email">Email ou Usuário</label>
|
||||
|
||||
23
templates/militantes.html
Normal file
23
templates/militantes.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!-- Botões de ação -->
|
||||
<td class="text-end">
|
||||
<div class="btn-group">
|
||||
<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 }}"
|
||||
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>
|
||||
@@ -1,4 +1,4 @@
|
||||
<!-- Modal de Edição -->
|
||||
<!-- Modal de Editar Militante -->
|
||||
<div class="modal fade" id="modalEditarMilitante" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
@@ -8,44 +8,259 @@
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form id="formEditarMilitante" method="POST">
|
||||
<input type="hidden" id="edit_militante_id" name="militante_id">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
|
||||
<div class="modal-body">
|
||||
<form id="formEditarMilitante" method="post">
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs nav-fill mb-3" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#edit-dados-basicos" type="button">
|
||||
<i class="fas fa-user me-2"></i>Dados Básicos
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#edit-contato" type="button">
|
||||
<i class="fas fa-address-book me-2"></i>Contato
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#edit-profissional" type="button">
|
||||
<i class="fas fa-briefcase me-2"></i>Profissional
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#edit-organizacao" type="button">
|
||||
<i class="fas fa-users me-2"></i>Organização
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Tab content -->
|
||||
<div class="tab-content">
|
||||
<!-- Dados Básicos -->
|
||||
<div class="tab-pane fade show active" id="edit-dados-basicos">
|
||||
<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>
|
||||
<label for="edit_nome" class="form-label">Nome</label>
|
||||
<input type="text" class="form-control" id="edit_nome" 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>
|
||||
<label for="edit_cpf" class="form-label">CPF</label>
|
||||
<input type="text" class="form-control" id="edit_cpf" 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>
|
||||
<label for="edit_titulo_eleitoral" class="form-label">Título Eleitoral</label>
|
||||
<input type="text" class="form-control" id="edit_titulo_eleitoral" name="titulo_eleitoral">
|
||||
</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">
|
||||
<label for="edit_data_nascimento" class="form-label">Data de Nascimento</label>
|
||||
<input type="date" class="form-control" id="edit_data_nascimento" name="data_nascimento">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="edit_data_entrada" class="form-label">Data de Entrada OCI</label>
|
||||
<input type="date" class="form-control" id="edit_data_entrada" name="data_entrada_oci">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="edit_data_efetivacao" class="form-label">Data de Efetivação</label>
|
||||
<input type="date" class="form-control" id="edit_data_efetivacao" name="data_efetivacao_oci">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contato -->
|
||||
<div class="tab-pane fade" id="edit-contato">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="edit_telefone1" class="form-label">Telefone Principal</label>
|
||||
<input type="text" class="form-control" id="edit_telefone1" name="telefone1">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="edit_telefone2" class="form-label">Telefone Alternativo</label>
|
||||
<input type="text" class="form-control" id="edit_telefone2" name="telefone2">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email Principal -->
|
||||
<div class="mb-3">
|
||||
<label for="edit_email" class="form-label">Email Principal</label>
|
||||
<input type="email" class="form-control" id="edit_email" name="email" required>
|
||||
</div>
|
||||
|
||||
<!-- Endereço -->
|
||||
<div class="endereco-container">
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="edit_cep" class="form-label">CEP</label>
|
||||
<input type="text" class="form-control" id="edit_cep" name="cep">
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="edit_estado" class="form-label">Estado</label>
|
||||
<select class="form-select" id="edit_estado" name="estado">
|
||||
<option value="">Selecione...</option>
|
||||
<!-- Estados serão carregados via JavaScript -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="edit_cidade" class="form-label">Cidade</label>
|
||||
<input type="text" class="form-control" id="edit_cidade" name="cidade">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="edit_bairro" class="form-label">Bairro</label>
|
||||
<input type="text" class="form-control" id="edit_bairro" name="bairro">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="edit_logradouro" class="form-label">Logradouro</label>
|
||||
<input type="text" class="form-control" id="edit_logradouro" name="rua">
|
||||
</div>
|
||||
<div class="col-md-2 mb-3">
|
||||
<label for="edit_numero" class="form-label">Número</label>
|
||||
<input type="text" class="form-control" id="edit_numero" name="numero">
|
||||
</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">
|
||||
<label for="edit_complemento" class="form-label">Complemento</label>
|
||||
<input type="text" class="form-control" id="edit_complemento" name="complemento">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Profissional -->
|
||||
<div class="tab-pane fade" id="edit-profissional">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="edit_profissao" class="form-label">Profissão</label>
|
||||
<input type="text" class="form-control" id="edit_profissao" name="profissao">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="edit_regime_trabalho" class="form-label">Regime de Trabalho</label>
|
||||
<select class="form-select" id="edit_regime_trabalho" name="regime_trabalho">
|
||||
<option value="">Selecione...</option>
|
||||
<option value="CLT">CLT</option>
|
||||
<option value="Estatutário">Estatutário</option>
|
||||
<option value="Terceirizado">Terceirizado</option>
|
||||
<option value="Autônomo">Autônomo</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="edit_empresa" class="form-label">Empresa</label>
|
||||
<input type="text" class="form-control" id="edit_empresa" name="empresa">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="edit_contratante" class="form-label">Contratante</label>
|
||||
<input type="text" class="form-control" id="edit_contratante" name="contratante">
|
||||
<small class="text-muted">Para terceirizados</small>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<!-- Dados Acadêmicos -->
|
||||
<div class="row">
|
||||
<div class="col-md-8 mb-3">
|
||||
<label for="edit_instituicao_ensino" class="form-label">Instituição de Ensino</label>
|
||||
<input type="text" class="form-control" id="edit_instituicao_ensino" name="instituicao_ensino">
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="edit_tipo_instituicao" class="form-label">Tipo</label>
|
||||
<select class="form-select" id="edit_tipo_instituicao" name="tipo_instituicao">
|
||||
<option value="">Selecione...</option>
|
||||
<option value="Federal">Federal</option>
|
||||
<option value="Estadual">Estadual</option>
|
||||
<option value="Municipal">Municipal</option>
|
||||
<option value="Privada">Privada</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Organização -->
|
||||
<div class="tab-pane fade" id="edit-organizacao">
|
||||
<!-- Dados Sindicais -->
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="edit_sindicato" class="form-label">Sindicato</label>
|
||||
<input type="text" class="form-control" id="edit_sindicato" name="sindicato">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="edit_cargo_sindical" class="form-label">Cargo Sindical</label>
|
||||
<input type="text" class="form-control" id="edit_cargo_sindical" name="cargo_sindical">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="edit_central_sindical" class="form-label">Central Sindical</label>
|
||||
<input type="text" class="form-control" id="edit_central_sindical" name="central_sindical">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3 d-flex align-items-center">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="edit_dirigente_sindical" name="dirigente_sindical">
|
||||
<label class="form-check-label" for="edit_dirigente_sindical">Dirigente Sindical</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<!-- Estado na Organização -->
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="edit_estado_militante" class="form-label">Estado</label>
|
||||
<select class="form-select" id="edit_estado_militante" name="estado">
|
||||
<option value="ativo">Ativo</option>
|
||||
<option value="licenciado">Licenciado</option>
|
||||
<option value="suspenso">Suspenso</option>
|
||||
<option value="desligado">Desligado</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="edit_celula" class="form-label">Célula</label>
|
||||
<select class="form-select" id="edit_celula" name="celula_id">
|
||||
<option value="">Selecione...</option>
|
||||
{% for celula in celulas %}
|
||||
<option value="{{ celula.id }}">{{ celula.nome }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label d-block">Responsabilidades</label>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="edit_resp_1" name="responsabilidades" value="1">
|
||||
<label class="form-check-label" for="edit_resp_1">Finanças</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="edit_resp_2" name="responsabilidades" value="2">
|
||||
<label class="form-check-label" for="edit_resp_2">Imprensa</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="edit_resp_4" name="responsabilidades" value="4">
|
||||
<label class="form-check-label" for="edit_resp_4">Quadro-Orientador</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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-success">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save me-2"></i>Salvar
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -10,38 +10,238 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="formNovoMilitante" method="post" action="{{ url_for('criar_militante') }}">
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs nav-fill mb-3" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#tab-dados-basicos" type="button">
|
||||
<i class="fas fa-user me-2"></i>Dados Básicos
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-contato" type="button">
|
||||
<i class="fas fa-address-book me-2"></i>Contato
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-profissional" type="button">
|
||||
<i class="fas fa-briefcase me-2"></i>Profissional
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab-organizacao" type="button">
|
||||
<i class="fas fa-users me-2"></i>Organização
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Tab content -->
|
||||
<div class="tab-content">
|
||||
<!-- Dados Básicos -->
|
||||
<div class="tab-pane fade show active" id="tab-dados-basicos">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="nome" class="form-label">Nome:</label>
|
||||
<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>
|
||||
<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>
|
||||
<label for="titulo_eleitoral" class="form-label">Título Eleitoral</label>
|
||||
<input type="text" class="form-control" id="titulo_eleitoral" name="titulo_eleitoral">
|
||||
</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">
|
||||
<label for="data_nascimento" class="form-label">Data de Nascimento</label>
|
||||
<input type="date" class="form-control" id="data_nascimento" name="data_nascimento">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="data_entrada" class="form-label">Data de Entrada OCI</label>
|
||||
<input type="date" class="form-control" id="data_entrada" name="data_entrada_oci">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="data_efetivacao" class="form-label">Data de Efetivação</label>
|
||||
<input type="date" class="form-control" id="data_efetivacao" name="data_efetivacao_oci">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contato -->
|
||||
<div class="tab-pane fade" id="tab-contato">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="telefone1" class="form-label">Telefone Principal</label>
|
||||
<input type="text" class="form-control" id="telefone1" name="telefone1">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="telefone2" class="form-label">Telefone Alternativo</label>
|
||||
<input type="text" class="form-control" id="telefone2" name="telefone2">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email Principal -->
|
||||
<div class="mb-3">
|
||||
<label for="endereco" class="form-label">Endereço:</label>
|
||||
<input type="text" class="form-control" id="endereco" name="endereco">
|
||||
<label for="email" class="form-label">Email Principal</label>
|
||||
<input type="email" class="form-control" id="email" name="email" required>
|
||||
</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>
|
||||
<!-- Endereço -->
|
||||
<div class="endereco-container">
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="cep" class="form-label">CEP</label>
|
||||
<input type="text" class="form-control" id="cep" name="cep">
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="estado" class="form-label">Estado</label>
|
||||
<select class="form-select" id="estado" name="estado">
|
||||
<option value="">Selecione...</option>
|
||||
<!-- Estados serão carregados via JavaScript -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="cidade" class="form-label">Cidade</label>
|
||||
<input type="text" class="form-control" id="cidade" name="cidade">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="bairro" class="form-label">Bairro</label>
|
||||
<input type="text" class="form-control" id="bairro" name="bairro">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="logradouro" class="form-label">Logradouro</label>
|
||||
<input type="text" class="form-control" id="logradouro" name="logradouro">
|
||||
</div>
|
||||
<div class="col-md-2 mb-3">
|
||||
<label for="numero" class="form-label">Número</label>
|
||||
<input type="text" class="form-control" id="numero" name="numero">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="complemento" class="form-label">Complemento</label>
|
||||
<input type="text" class="form-control" id="complemento" name="complemento">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Profissional -->
|
||||
<div class="tab-pane fade" id="tab-profissional">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="profissao" class="form-label">Profissão</label>
|
||||
<input type="text" class="form-control" id="profissao" name="profissao">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="regime_trabalho" class="form-label">Regime de Trabalho</label>
|
||||
<select class="form-select" id="regime_trabalho" name="regime_trabalho">
|
||||
<option value="">Selecione...</option>
|
||||
<option value="CLT">CLT</option>
|
||||
<option value="Estatutário">Estatutário</option>
|
||||
<option value="Terceirizado">Terceirizado</option>
|
||||
<option value="Autônomo">Autônomo</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="empresa" class="form-label">Empresa</label>
|
||||
<input type="text" class="form-control" id="empresa" name="empresa">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="contratante" class="form-label">Contratante</label>
|
||||
<input type="text" class="form-control" id="contratante" name="contratante">
|
||||
<small class="text-muted">Para terceirizados</small>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<!-- Dados Acadêmicos -->
|
||||
<div class="row">
|
||||
<div class="col-md-8 mb-3">
|
||||
<label for="instituicao_ensino" class="form-label">Instituição de Ensino</label>
|
||||
<input type="text" class="form-control" id="instituicao_ensino" name="instituicao_ensino">
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="tipo_instituicao" class="form-label">Tipo</label>
|
||||
<select class="form-select" id="tipo_instituicao" name="tipo_instituicao">
|
||||
<option value="">Selecione...</option>
|
||||
<option value="Federal">Federal</option>
|
||||
<option value="Estadual">Estadual</option>
|
||||
<option value="Municipal">Municipal</option>
|
||||
<option value="Privada">Privada</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Organização -->
|
||||
<div class="tab-pane fade" id="tab-organizacao">
|
||||
<!-- Dados Sindicais -->
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="sindicato" class="form-label">Sindicato</label>
|
||||
<input type="text" class="form-control" id="sindicato" name="sindicato">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="cargo_sindical" class="form-label">Cargo Sindical</label>
|
||||
<input type="text" class="form-control" id="cargo_sindical" name="cargo_sindical">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="central_sindical" class="form-label">Central Sindical</label>
|
||||
<input type="text" class="form-control" id="central_sindical" name="central_sindical">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3 d-flex align-items-center">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="dirigente_sindical" name="dirigente_sindical">
|
||||
<label class="form-check-label" for="dirigente_sindical">Dirigente Sindical</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<!-- Estado na Organização -->
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="estado_militante" class="form-label">Estado</label>
|
||||
<select class="form-select" id="estado_militante" name="estado">
|
||||
<option value="ATIVO">Ativo</option>
|
||||
<option value="DESLIGADO">Desligado</option>
|
||||
<option value="SUSPENSO">Suspenso</option>
|
||||
<option value="AFASTADO">Afastado</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="celula" class="form-label">Célula</label>
|
||||
<select class="form-select" id="celula" name="celula_id" required>
|
||||
<option value="">Selecione...</option>
|
||||
{% for celula in celulas %}
|
||||
<option value="{{ celula.id }}">{{ celula.nome }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label d-block">Responsabilidades</label>
|
||||
<div class="row g-3">
|
||||
{% for valor, nome in Militante.get_responsabilidades_list() %}
|
||||
<div class="col-md-6">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="resp_{{ valor }}"
|
||||
name="responsabilidades" value="{{ valor }}">
|
||||
<label class="form-check-label" for="resp_{{ valor }}">{{ nome }}</label>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user