Files
controles/templates/listar_militantes.html

613 lines
25 KiB
HTML
Raw Normal View History

{% extends "base.html" %}
{% block title %}Militantes{% endblock %}
{% block content %}
<div class="row mb-4">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center">
<h1 class="mb-0">
<i class="fas fa-users me-2"></i>Militantes
</h1>
{% if current_user.has_permission('gerenciar_militantes') %}
<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
</button>
{% endif %}
</div>
</div>
</div>
<div class="card shadow-sm">
<div class="card-body">
<div class="row mb-4">
<div class="col-md-6">
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-search"></i>
</span>
<input type="text" class="form-control" id="searchInput" placeholder="Pesquisar militantes...">
</div>
</div>
<div class="col-md-6">
<div class="d-flex justify-content-end gap-2">
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
<i class="fas fa-filter me-1"></i>Filtrar
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" data-filter="todos">Todos</a></li>
<li><a class="dropdown-item" href="#" data-filter="filiados">Filiados</a></li>
<li><a class="dropdown-item" href="#" data-filter="nao-filiados">Não Filiados</a></li>
</ul>
</div>
<button type="button" class="btn btn-outline-secondary" id="btnExportar">
<i class="fas fa-download me-1"></i>Exportar
</button>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover" id="militantesTable">
<thead>
<tr>
<th data-sort="nome">Nome <i class="fas fa-sort"></i></th>
<th data-sort="cpf">CPF <i class="fas fa-sort"></i></th>
<th data-sort="email">Email <i class="fas fa-sort"></i></th>
<th data-sort="telefone">Telefone <i class="fas fa-sort"></i></th>
<th data-sort="celula">Célula <i class="fas fa-sort"></i></th>
<th>Responsabilidades</th>
<th class="text-end">Ações</th>
</tr>
</thead>
<tbody>
{% for militante in militantes %}
<tr data-militante="{{ militante.id }}" data-filiado="{{ 'sim' if militante.filiado else 'nao' }}">
<td data-nome="{{ militante.nome }}">{{ militante.nome }}</td>
<td data-cpf="{{ militante.cpf }}">{{ militante.cpf }}</td>
<td data-email="{{ militante.email }}">{{ militante.email }}</td>
<td data-telefone="{{ militante.telefone }}">{{ militante.telefone }}</td>
<td data-celula="{{ militante.celula.nome }}">{{ militante.celula.nome }}</td>
<td>
{% if militante.responsabilidades & Militante.RESPONSAVEL_FINANCAS %}
<span class="badge bg-primary">Finanças</span>
{% endif %}
{% if militante.responsabilidades & Militante.RESPONSAVEL_IMPRENSA %}
<span class="badge bg-info">Imprensa</span>
{% endif %}
{% if militante.responsabilidades & Militante.QUADRO_ORIENTADOR %}
<span class="badge bg-success">Quadro-Orientador</span>
{% endif %}
</td>
<td class="text-end">
<div class="btn-group">
{% if current_user.has_permission('gerenciar_militantes') %}
<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>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="d-flex justify-content-between align-items-center mt-4">
<div class="text-muted">
Mostrando <span id="countMilitantes">{{ militantes|length }}</span> militantes
</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>
</div>
<!-- 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>
<!-- 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>
{% endblock %}
{% block scripts %}
<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>
{% endblock %}
{% block extra_css %}
<style>
/* 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;
}
/* Estilo para colunas ordenáveis */
th[data-sort] {
cursor: pointer;
user-select: none;
}
th[data-sort] i {
margin-left: 5px;
color: #ccc;
}
th[data-sort].sort-asc i,
th[data-sort].sort-desc i {
color: var(--primary-color);
}
/* Animação para linhas da tabela */
#militantesTable tbody tr {
transition: all 0.3s ease;
}
#militantesTable tbody tr:hover {
background-color: rgba(0,0,0,0.02);
transform: translateX(5px);
}
/* Estilo para badges */
.badge {
font-weight: 500;
padding: 0.5em 0.8em;
}
/* Estilo para botões de ação */
.btn-group .btn {
padding: 0.25rem 0.5rem;
}
.btn-group .btn i {
width: 16px;
text-align: center;
}
/* Estilo para modais */
.modal-content {
border: none;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.modal-header {
background: linear-gradient(to right, var(--bs-gray-dark), var(--bs-gray));
color: white;
border-radius: 12px 12px 0 0;
border-bottom: none;
padding: 1rem;
}
.modal-body {
padding: 1.5rem;
}
.modal-footer {
border-top: 1px solid #eee;
padding: 1rem;
}
/* Responsividade */
@media (max-width: 768px) {
.btn-group {
display: flex;
margin-top: 1rem;
}
.btn-group .btn {
flex: 1;
}
.modal-footer {
justify-content: center;
}
.modal-footer .btn {
min-width: 120px;
}
}
</style>
{% endblock %}