Browse Source

Modal de explicação na criação de Tipo de Proposição

pull/3858/head
rangelbruno 11 months ago
parent
commit
7c6f32ca1b
  1. 3
      sapl/base/urls.py
  2. 151
      sapl/base/views.py
  3. 4
      sapl/materia/urls.py
  4. 9
      sapl/parlamentares/urls.py
  5. 158
      sapl/parlamentares/views.py
  6. 159
      sapl/templates/404.html
  7. 127
      sapl/templates/500.html
  8. 756
      sapl/templates/auth/user_form.html
  9. 166
      sapl/templates/fluxo_proposicoes.html
  10. 9
      sapl/templates/materia/proposicao_form.html
  11. 349
      sapl/templates/materia/tipomaterialegislativa_form.html
  12. 627
      sapl/templates/materia/tipoproposicao_form.html
  13. 465
      sapl/templates/parlamentares/vincular_parlamentar.html

3
sapl/base/urls.py

@ -8,7 +8,7 @@ from django.views.generic.base import RedirectView, TemplateView
from sapl.base.views import (AutorCrud, ConfirmarEmailView, TipoAutorCrud, get_estatistica,
RecuperarSenhaEmailView, RecuperarSenhaFinalizadoView,
RecuperarSenhaConfirmaView, RecuperarSenhaCompletoView, IndexView, UserCrud,
GuiaProjetoView, FluxoProposicoesView)
GuiaProjetoView, FluxoProposicoesView, CriarAutorAjaxView)
from sapl.settings import MEDIA_URL, LOGOUT_REDIRECT_URL
from .apps import AppConfig
from .views import (LoginSapl, AlterarSenha, AppConfigCrud, CasaLegislativaCrud,
@ -50,6 +50,7 @@ urlpatterns = [
url(r'^$', IndexView.as_view(template_name='index.html'), name='sapl_index'),
url(r'^sistema/autor/tipo/', include(TipoAutorCrud.get_urls())),
url(r'^sistema/autor/criar-ajax/$', CriarAutorAjaxView.as_view(), name='criar_autor_ajax'),
url(r'^sistema/autor/', include(AutorCrud.get_urls())),
url(r'^sistema/guia-projeto/$', GuiaProjetoView.as_view(),

151
sapl/base/views.py

@ -1587,3 +1587,154 @@ def pesquisa_textual(request):
json_dict['resultados'].append(sec_dict)
return JsonResponse(json_dict)
@method_decorator(ratelimit(key=ratelimit_ip,
rate=RATE_LIMITER_RATE,
method='POST',
block=True), name='dispatch')
class CriarAutorAjaxView(PermissionRequiredMixin, FormView):
"""
View AJAX para criar Autor rapidamente na tela de edição de usuário.
Suporta tanto autores com content_type (Parlamentar, Comissão, etc)
quanto autores genéricos (nome e cargo).
"""
permission_required = ('base.add_autor',)
logger = logging.getLogger(__name__)
def get(self, request, *args, **kwargs):
"""
Retorna lista de usuários ativos para seleção no modal.
"""
User = get_user_model()
usuarios = User.objects.filter(is_active=True).order_by('first_name', 'username')
usuarios_data = []
for user in usuarios:
nome_completo = f"{user.first_name} {user.last_name}".strip() if user.first_name else user.username
usuarios_data.append({
'id': user.id,
'username': user.username,
'first_name': user.first_name,
'last_name': user.last_name,
'nome_completo': nome_completo
})
return JsonResponse({
'success': True,
'count': len(usuarios_data),
'results': usuarios_data
})
def post(self, request, *args, **kwargs):
from django.contrib.contenttypes.models import ContentType
try:
tipo_autor_id = request.POST.get('tipo_autor')
autor_related_id = request.POST.get('autor_related')
nome = request.POST.get('nome', '').strip()
cargo = request.POST.get('cargo', '').strip()
if not tipo_autor_id:
return JsonResponse({
'success': False,
'error': 'Tipo de Autor é obrigatório.'
}, status=400)
# Buscar TipoAutor
try:
tipo_autor = TipoAutor.objects.get(pk=tipo_autor_id)
except TipoAutor.DoesNotExist:
return JsonResponse({
'success': False,
'error': 'Tipo de Autor não encontrado.'
}, status=404)
# Verificar se tipo tem content_type
if tipo_autor.content_type:
# Tipo com content_type (Parlamentar, Comissão, etc)
if not autor_related_id:
return JsonResponse({
'success': False,
'error': f'Selecione um registro de {tipo_autor.descricao}.'
}, status=400)
# Buscar o registro relacionado
model_class = tipo_autor.content_type.model_class()
try:
related_obj = model_class.objects.get(pk=autor_related_id)
except model_class.DoesNotExist:
return JsonResponse({
'success': False,
'error': f'Registro de {tipo_autor.descricao} não encontrado.'
}, status=404)
# Verificar se já existe autor para este registro
if Autor.objects.filter(
content_type=tipo_autor.content_type,
object_id=autor_related_id
).exists():
return JsonResponse({
'success': False,
'error': f'Já existe um autor cadastrado para "{related_obj}".'
}, status=400)
# Criar autor com content_type
autor = Autor.objects.create(
tipo=tipo_autor,
content_type=tipo_autor.content_type,
object_id=autor_related_id,
nome=str(related_obj)
)
self.logger.info(
f"user={request.user.username}. Autor '{autor.nome}' criado "
f"via AJAX vinculado a {tipo_autor.descricao} (ID: {autor.id})"
)
else:
# Tipo genérico (sem content_type)
if not nome:
return JsonResponse({
'success': False,
'error': 'Nome do Autor é obrigatório.'
}, status=400)
# Verificar se já existe autor com este nome
if Autor.objects.filter(nome=nome).exists():
return JsonResponse({
'success': False,
'error': f'Já existe um autor com o nome "{nome}".'
}, status=400)
# Criar autor genérico
autor = Autor.objects.create(
tipo=tipo_autor,
nome=nome,
cargo=cargo if cargo else ''
)
self.logger.info(
f"user={request.user.username}. Autor genérico '{autor.nome}' "
f"criado via AJAX (ID: {autor.id})"
)
return JsonResponse({
'success': True,
'autor': {
'id': autor.id,
'nome': autor.nome,
'tipo': str(autor.tipo),
'cargo': autor.cargo if autor.cargo else ''
}
})
except Exception as e:
self.logger.error(
f"user={request.user.username}. Erro ao criar autor via AJAX: {str(e)}"
)
return JsonResponse({
'success': False,
'error': f'Erro ao criar autor: {str(e)}'
}, status=500)

4
sapl/materia/urls.py

@ -166,9 +166,13 @@ urlpatterns_sistema = [
include(AssuntoMateriaCrud.get_urls())),
url(r'^sistema/proposicao/tipo/',
include(TipoProposicaoCrud.get_urls())),
url(r'^sistema/materia/tipoproposicao/',
include(TipoProposicaoCrud.get_urls())),
url(r'^sistema/materia/tipo/', include(TipoMateriaCrud.get_urls())),
url(r'^sistema/materia/regime-tramitacao/',
include(RegimeTramitacaoCrud.get_urls())),
url(r'^sistema/materia/regimetramitacao/',
include(RegimeTramitacaoCrud.get_urls())),
url(r'^sistema/materia/tipo-documento/',
include(TipoDocumentoCrud.get_urls())),
url(r'^sistema/materia/tipo-fim-relatoria/',

9
sapl/parlamentares/urls.py

@ -23,7 +23,8 @@ from sapl.parlamentares.views import (CargoMesaCrud, ColigacaoCrud,
PesquisarParlamentarView, VincularParlamentarView,
get_sessoes_legislatura, FrenteCargoCrud, FrenteParlamentarCrud,
get_parlamentar_frentes, PesquisarColigacaoView, PesquisarPartidoView,
BlocoCargoCrud, BlocoMembroCrud)
BlocoCargoCrud, BlocoMembroCrud, criar_parlamentar_ajax,
criar_legislatura_ajax)
from .apps import AppConfig
@ -56,6 +57,12 @@ urlpatterns = [
url(r'^parlamentar/vincular-parlamentar/$',
VincularParlamentarView.as_view(), name='vincular_parlamentar'),
url(r'^parlamentar/criar-parlamentar-ajax/$',
criar_parlamentar_ajax, name='criar_parlamentar_ajax'),
url(r'^parlamentar/criar-legislatura-ajax/$',
criar_legislatura_ajax, name='criar_legislatura_ajax'),
url(r'^parlamentar/coligacao-legislatura/',
coligacao_legislatura, name="coligacao_legislatura"),
url(r'^sistema/coligacao/', include(ColigacaoCrud.get_urls() +

158
sapl/parlamentares/views.py

@ -3,6 +3,7 @@ import json
import logging
from django.contrib import messages
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType
@ -1470,6 +1471,163 @@ class VincularParlamentarView(PermissionRequiredMixin, FormView):
return HttpResponseRedirect(self.get_success_url())
@permission_required('parlamentares.add_parlamentar')
def criar_parlamentar_ajax(request):
"""View AJAX para criar parlamentar via modal"""
if request.method == 'POST':
try:
nome_completo = request.POST.get('nome_completo', '').strip()
nome_parlamentar = request.POST.get('nome_parlamentar', '').strip()
sexo = request.POST.get('sexo', '').strip()
# Validações básicas dos campos obrigatórios
if not nome_completo or not nome_parlamentar or not sexo:
return JsonResponse({
'success': False,
'error': 'Nome Completo, Nome Parlamentar e Sexo são obrigatórios.'
})
if sexo not in ['F', 'M']:
return JsonResponse({
'success': False,
'error': 'Sexo inválido. Use F (Feminino) ou M (Masculino).'
})
# Campos opcionais
cpf = request.POST.get('cpf', '').strip()
rg = request.POST.get('rg', '').strip()
data_nascimento = request.POST.get('data_nascimento', '').strip()
email = request.POST.get('email', '').strip()
telefone = request.POST.get('telefone', '').strip()
telefone_celular = request.POST.get('telefone_celular', '').strip()
# Processar data de nascimento se fornecida
data_nascimento_obj = None
if data_nascimento:
try:
data_nascimento_obj = datetime.strptime(data_nascimento, '%d/%m/%Y').date()
except ValueError:
return JsonResponse({
'success': False,
'error': 'Formato de data de nascimento inválido. Use DD/MM/AAAA.'
})
# Criar parlamentar com todos os campos
parlamentar = Parlamentar.objects.create(
nome_completo=nome_completo,
nome_parlamentar=nome_parlamentar,
sexo=sexo,
cpf=cpf,
rg=rg,
data_nascimento=data_nascimento_obj,
email=email,
telefone=telefone,
telefone_celular=telefone_celular,
ativo=True # Por padrão, marcar como ativo
)
return JsonResponse({
'success': True,
'parlamentar': {
'id': parlamentar.id,
'nome': str(parlamentar)
}
})
except Exception as e:
return JsonResponse({
'success': False,
'error': f'Erro ao criar parlamentar: {str(e)}'
})
return JsonResponse({'success': False, 'error': 'Método não permitido'})
@permission_required('parlamentares.add_parlamentar')
def criar_legislatura_ajax(request):
"""View AJAX para criar legislatura via modal"""
if request.method == 'POST':
try:
numero = request.POST.get('numero', '').strip()
data_inicio = request.POST.get('data_inicio', '').strip()
data_fim = request.POST.get('data_fim', '').strip()
data_eleicao = request.POST.get('data_eleicao', '').strip()
# Validações básicas
if not numero or not data_inicio or not data_fim or not data_eleicao:
return JsonResponse({
'success': False,
'error': 'Todos os campos são obrigatórios.'
})
try:
numero = int(numero)
if numero <= 0:
raise ValueError
except ValueError:
return JsonResponse({
'success': False,
'error': 'Número deve ser um valor inteiro positivo.'
})
# Converter datas do formato brasileiro (DD/MM/YYYY) para YYYY-MM-DD
from datetime import datetime
try:
data_inicio_obj = datetime.strptime(data_inicio, '%d/%m/%Y').date()
data_fim_obj = datetime.strptime(data_fim, '%d/%m/%Y').date()
data_eleicao_obj = datetime.strptime(data_eleicao, '%d/%m/%Y').date()
except ValueError:
return JsonResponse({
'success': False,
'error': 'Formato de data inválido. Use DD/MM/YYYY.'
})
# Validar ordem das datas
if data_inicio_obj >= data_fim_obj:
return JsonResponse({
'success': False,
'error': 'Data de Início deve ser anterior à Data Fim.'
})
if data_eleicao_obj >= data_inicio_obj:
return JsonResponse({
'success': False,
'error': 'Data de Eleição deve ser anterior à Data de Início.'
})
# Verificar se já existe legislatura com esse número
if Legislatura.objects.filter(numero=numero).exists():
return JsonResponse({
'success': False,
'error': f'Já existe uma legislatura com o número {numero}.'
})
# Criar legislatura
legislatura = Legislatura.objects.create(
numero=numero,
data_inicio=data_inicio_obj,
data_fim=data_fim_obj,
data_eleicao=data_eleicao_obj
)
return JsonResponse({
'success': True,
'legislatura': {
'id': legislatura.id,
'nome': str(legislatura)
}
})
except Exception as e:
return JsonResponse({
'success': False,
'error': f'Erro ao criar legislatura: {str(e)}'
})
return JsonResponse({'success': False, 'error': 'Método não permitido'})
class BlocoCrud(CrudAux):
model = Bloco
public = [RP_DETAIL, RP_LIST]

159
sapl/templates/404.html

@ -1,147 +1,12 @@
{% load i18n menus %}
{% load common_tags %}
{% load render_bundle from webpack_loader %}
{% load webpack_static from webpack_loader %}
<!DOCTYPE html>
<!--[if IE 8]> <html class="no-js lt-ie9" lang="pt-br"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js" lang="pt-br">
<!--<![endif]-->
<head>
<meta charset="utf-8">
<title>{% block head_title %}{% trans 'SAPL - Sistema de Apoio ao Processo Legislativo' %}{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% block head_content %}
<link rel="icon" href="{% webpack_static 'img/favicon.ico' %}" type="image/png" >
{% render_chunk_vendors 'css' %}
{% render_bundle 'global' 'css' %}
{% endblock %}
</head>
<body>
<div class="page fadein">
{% if not request|has_iframe %}
{% block navigation %}
<nav class="navbar navbar-inverse navbar-static-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div id="navbar" class="navbar-collapse collapse">
</div><!--/.nav-collapse -->
</div>
</nav>
{% endblock navigation %}
{# Header #}
{% block main_header %}
<header class="masthead">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/">
<img src="{% if logotipo %}{{ MEDIA_URL }}{{ logotipo }}{% else %}{% webpack_static 'img/logo.png' %}{% endif %}"
alt="Logo" class="img-responsive visible-md-inline-block visible-lg-inline-block" >
<span class="vcenter">
{# XXX Make better use of translation tags in html blocks ie. actually use the proper blocktrans tag efficiently #}
<br/><small>{% trans 'Sistema de Apoio ao Processo Legislativo' %}</small>
</span>
</a>
</div>
<div class="hidden-print">
{% block sections_nav %} {% subnav %} {% endblock sections_nav %}
</div>
</div>
</header>
{% endblock main_header %}
{% else %}
<div class="btn-cancel-iframe">
<a href="?iframe=0" target="_blank"><i class="fa fa-2x fa-arrows-alt"></i></a>
</div>
<header class="masthead">
<div class="container">
<div class="hidden-print">
{% subnav %}
</div>
</div>
</header>
{% endif %}
<div class="container">
<span class="text-center">
<br/><h1><big>{% trans 'Página não encontrada! Erro 404' %}</big></h1>
</span>
</div>
{% block base_content %}
{% endblock %}
{% if not request|has_iframe %}
{% block footer_container %}
<footer id="footer" class="footer page__row hidden-print">
<div class="container">
<div class="row">
<div class="col-md-4">
<a class="footer__logo" href="#">
<a href="http://www.interlegis.leg.br/">
<img src="{% webpack_static 'img/logo_interlegis.png' %}" alt="{% trans 'Logo do Interlegis' %} ">
</a>
</a>
<p>
<small>
Desenvolvido pelo <a href="http://www.interlegis.leg.br/">Interlegis</a> em software livre e aberto.
</small>
</p>
</div>
<div class="col-md-4">
<a class="footer__logo" href="#">
<img src="{% webpack_static 'img/logo_cc.png' %}" alt="{% trans 'Logo do Creative Commons BY SA' %}">
</a>
<p>
<small>
Conteúdo e dados sob licença <a href="https://creativecommons.org">Creative Commons</a> 4.0 <a href="https://creativecommons.org/licenses/by/4.0/">Atribuir Fonte - Compartilhar Igual</a>
</small>
</p>
</div>
</div>
</div>
</footer>
</div>
{% endblock footer_container %}
{% endif %}
{% block foot_js %}
{% render_chunk_vendors 'js' %}
{% render_bundle 'global' 'js' %}
{% block extra_js %}{% endblock %}
<script type="text/javascript" >
function inIframe () {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}
$(document).ready(function(){
let iframe_set_backend = {{ request|has_iframe|lower }}
if (iframe_set_backend && !inIframe() ) {
location.href = location.origin + '?iframe=0'
}
});
</script>
{% endblock foot_js %}
</body>
</html>
{% extends "base.html" %}
{% load i18n %}
{% block head_title %}{% trans 'Página não encontrada! Erro 404' %}{% endblock %}
{% block base_content %}
<div class="container text-center" style="margin-top: 50px; margin-bottom: 50px;">
<h1><big>{% trans 'Página não encontrada! Erro 404' %}</big></h1>
<p>{% trans 'A página que você está procurando não foi encontrada.' %}</p>
<a href="/" class="btn btn-primary">{% trans 'Voltar para a página inicial' %}</a>
</div>
{% endblock %}

127
sapl/templates/500.html

@ -1,115 +1,12 @@
{% load i18n menus %}
{% load common_tags %}
{% load render_bundle from webpack_loader %}
{% load webpack_static from webpack_loader %}
<!DOCTYPE html>
<!--[if IE 8]> <html class="no-js lt-ie9" lang="pt-br"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js" lang="pt-br">
<!--<![endif]-->
<head>
<meta charset="utf-8">
<title>{% block head_title %}{% trans 'SAPL - Sistema de Apoio ao Processo Legislativo' %}{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% block head_content %}
<link rel="icon" href="{% webpack_static 'img/favicon.ico' %}" type="image/png" >
{% render_chunk_vendors 'css' %}
{% render_bundle 'global' 'css' %}
{% endblock %}
</head>
<body>
<div class="page fadein">
{% block navigation %}
<nav class="navbar navbar-inverse navbar-static-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div id="navbar" class="navbar-collapse collapse">
</div><!--/.nav-collapse -->
</div>
</nav>
{% endblock navigation %}
{# Header #}
{% block main_header %}
<header class="masthead">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/">
<span class="text-center">
<br/><small>{% trans 'Sistema de Apoio ao Processo Legislativo' %}</small>
</span>
</a>
</div>
<div class="hidden-print">
{% block sections_nav %} {% subnav %} {% endblock sections_nav %}
</div>
</div>
</header>
{% endblock main_header %}
<div class="container">
<span class="text-center">
<br/><h1><big>{% trans 'Ocorreu um erro inesperado! Erro 500' %}</big></h1>
</span>
</div>
{% block base_content %}
{% endblock %}
{% block footer_container %}
<footer id="footer" class="footer page__row hidden-print">
<div class="container">
<div class="row">
<div class="col-md-4">
<a class="footer__logo" href="#">
<a href="http://www.interlegis.leg.br/">
<img src="{% webpack_static 'img/logo_interlegis.png' %}" alt="{% trans 'Logo do Interlegis' %} ">
</a>
</a>
<p>
<small>
Desenvolvido pelo <a href="http://www.interlegis.leg.br/">Interlegis</a> em software livre e aberto.
</small>
</p>
</div>
<div class="col-md-4">
<a class="footer__logo" href="#">
<img src="{% webpack_static 'img/logo_cc.png' %}" alt="{% trans 'Logo do Creative Commons BY SA' %}">
</a>
<p>
<small>
Conteúdo e dados sob licença <a href="https://creativecommons.org">Creative Commons</a> 4.0 <a href="https://creativecommons.org/licenses/by/4.0/">Atribuir Fonte - Compartilhar Igual</a>
</small>
</p>
</div>
</div>
</div>
</footer>
</div>
{% endblock footer_container %}
{% block foot_js %}
{% render_chunk_vendors 'js' %}
{% render_bundle 'global' 'js' %}
{% block extra_js %}{% endblock %}
{% endblock foot_js %}
</body>
</html>
{% extends "base.html" %}
{% load i18n %}
{% block head_title %}{% trans 'Ocorreu um erro inesperado! Erro 500' %}{% endblock %}
{% block base_content %}
<div class="container text-center" style="margin-top: 50px; margin-bottom: 50px;">
<h1><big>{% trans 'Ocorreu um erro inesperado! Erro 500' %}</big></h1>
<p>{% trans 'Desculpe, ocorreu um erro interno no servidor. Tente novamente mais tarde.' %}</p>
<a href="/" class="btn btn-primary">{% trans 'Voltar para a página inicial' %}</a>
</div>
{% endblock %}

756
sapl/templates/auth/user_form.html

@ -1,10 +1,332 @@
{% extends "crud/form.html" %}
{% load i18n %}
{% block base_content %}
{{ block.super }}
<!-- Modal de Ajuda sobre Grupos de Permissões -->
<div class="modal fade" id="modalAjudaGrupos" tabindex="-1" role="dialog" aria-labelledby="modalAjudaGruposLabel" aria-hidden="true">
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header bg-info text-white">
<h5 class="modal-title" id="modalAjudaGruposLabel">
<i class="fas fa-info-circle"></i> Grupos de Permissões - Guia de Referência
</h5>
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Fechar">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p class="lead mb-4">Cada grupo de permissões concede acesso a funcionalidades específicas do SAPL. Selecione os grupos adequados conforme as responsabilidades do usuário.</p>
<div class="row">
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header bg-primary text-white">
<strong><i class="fas fa-users-cog"></i> Operador Administrativo</strong>
</div>
<div class="card-body">
<p><strong>Responsabilidade:</strong> Gerenciar documentos e processos administrativos internos.</p>
<p><strong>Funcionalidades:</strong></p>
<ul>
<li>Criar e tramitar documentos administrativos</li>
<li>Gerenciar protocolo administrativo</li>
<li>Acompanhar processos internos da Casa Legislativa</li>
</ul>
<p class="text-muted mb-0"><small><strong>Quando usar:</strong> Para funcionários que lidam com a parte administrativa e burocrática da Casa.</small></p>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header bg-success text-white">
<strong><i class="fas fa-globe"></i> Operador Geral</strong>
</div>
<div class="card-body">
<p><strong>Responsabilidade:</strong> Acesso amplo a todas as funcionalidades principais do sistema.</p>
<p><strong>Funcionalidades:</strong></p>
<ul>
<li>Cadastrar e editar matérias legislativas</li>
<li>Gerenciar normas jurídicas</li>
<li>Configurar sessões plenárias</li>
<li>Acesso a relatórios e consultas gerais</li>
</ul>
<p class="text-muted mb-0"><small><strong>Quando usar:</strong> Para funcionários de confiança que precisam de acesso abrangente ao sistema.</small></p>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header bg-warning text-dark">
<strong><i class="fas fa-gavel"></i> Operador de Audiência</strong>
</div>
<div class="card-body">
<p><strong>Responsabilidade:</strong> Gerenciar audiências públicas.</p>
<p><strong>Funcionalidades:</strong></p>
<ul>
<li>Cadastrar e agendar audiências públicas</li>
<li>Registrar participantes e convidados</li>
<li>Documentar decisões e encaminhamentos</li>
<li>Gerar relatórios de audiências</li>
</ul>
<p class="text-muted mb-0"><small><strong>Quando usar:</strong> Para funcionários responsáveis pela organização de audiências públicas.</small></p>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header bg-info text-white">
<strong><i class="fas fa-users"></i> Operador de Comissões</strong>
</div>
<div class="card-body">
<p><strong>Responsabilidade:</strong> Gerenciar comissões parlamentares e suas atividades.</p>
<p><strong>Funcionalidades:</strong></p>
<ul>
<li>Criar e configurar comissões (permanentes, temporárias, especiais)</li>
<li>Gerenciar composição e membros</li>
<li>Registrar reuniões de comissão</li>
<li>Acompanhar matérias em comissão</li>
</ul>
<p class="text-muted mb-0"><small><strong>Quando usar:</strong> Para secretários de comissões ou assessores responsáveis.</small></p>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header bg-danger text-white">
<strong><i class="fas fa-file-alt"></i> Operador de Matéria</strong>
</div>
<div class="card-body">
<p><strong>Responsabilidade:</strong> Gerenciar matérias legislativas (projetos, requerimentos, etc.).</p>
<p><strong>Funcionalidades:</strong></p>
<ul>
<li>Cadastrar e editar matérias legislativas</li>
<li>Controlar tramitação de matérias</li>
<li>Anexar documentos e pareceres</li>
<li>Registrar autoria e relatoria</li>
</ul>
<p class="text-muted mb-0"><small><strong>Quando usar:</strong> Para funcionários do setor de expediente legislativo.</small></p>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header bg-secondary text-white">
<strong><i class="fas fa-balance-scale"></i> Operador de Norma Jurídica</strong>
</div>
<div class="card-body">
<p><strong>Responsabilidade:</strong> Gerenciar normas jurídicas publicadas.</p>
<p><strong>Funcionalidades:</strong></p>
<ul>
<li>Cadastrar leis, decretos, resoluções, etc.</li>
<li>Vincular normas às matérias de origem</li>
<li>Publicar textos compilados e consolidados</li>
<li>Gerenciar vigência e revogações</li>
</ul>
<p class="text-muted mb-0"><small><strong>Quando usar:</strong> Para funcionários da assessoria jurídica ou setor de publicações.</small></p>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header bg-dark text-white">
<strong><i class="fas fa-tv"></i> Operador de Painel Eletrônico</strong>
</div>
<div class="card-body">
<p><strong>Responsabilidade:</strong> Controlar painéis de votação e exibição.</p>
<p><strong>Funcionalidades:</strong></p>
<ul>
<li>Controlar painel eletrônico do plenário</li>
<li>Gerenciar votações eletrônicas</li>
<li>Exibir ordem do dia e resultados</li>
<li>Registrar presença de parlamentares</li>
</ul>
<p class="text-muted mb-0"><small><strong>Quando usar:</strong> Para operadores técnicos do plenário durante sessões.</small></p>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header bg-primary text-white">
<strong><i class="fas fa-inbox"></i> Operador de Protocolo Administrativo</strong>
</div>
<div class="card-body">
<p><strong>Responsabilidade:</strong> Protocolar e encaminhar documentos administrativos.</p>
<p><strong>Funcionalidades:</strong></p>
<ul>
<li>Protocolar documentos administrativos</li>
<li>Gerar números de protocolo</li>
<li>Encaminhar para setores competentes</li>
<li>Consultar histórico de protocolos</li>
</ul>
<p class="text-muted mb-0"><small><strong>Quando usar:</strong> Para atendentes do setor de protocolo.</small></p>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header bg-success text-white">
<strong><i class="fas fa-calendar-check"></i> Operador de Sessão Plenária</strong>
</div>
<div class="card-body">
<p><strong>Responsabilidade:</strong> Gerenciar sessões plenárias e suas atas.</p>
<p><strong>Funcionalidades:</strong></p>
<ul>
<li>Criar e configurar sessões plenárias</li>
<li>Definir ordem do dia e expediente</li>
<li>Registrar votações e deliberações</li>
<li>Gerar e publicar atas de sessão</li>
</ul>
<p class="text-muted mb-0"><small><strong>Quando usar:</strong> Para secretários de plenário e taquígrafos.</small></p>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header bg-info text-white">
<strong><i class="fas fa-user-shield"></i> Usuários com Login Social</strong>
</div>
<div class="card-body">
<p><strong>Responsabilidade:</strong> Acesso público limitado via redes sociais.</p>
<p><strong>Funcionalidades:</strong></p>
<ul>
<li>Login via Google, Facebook, etc.</li>
<li>Acesso somente-leitura a informações públicas</li>
<li>Consulta de matérias e normas</li>
<li>Sem permissões de edição ou cadastro</li>
</ul>
<p class="text-muted mb-0"><small><strong>Quando usar:</strong> Para cidadãos que desejam acompanhar atividades legislativas.</small></p>
</div>
</div>
</div>
</div>
<div class="alert alert-warning mt-3">
<strong><i class="fas fa-exclamation-triangle"></i> Atenção:</strong>
<ul class="mb-0">
<li>Um usuário pode pertencer a múltiplos grupos simultaneamente.</li>
<li>As permissões são cumulativas - o usuário terá acesso a todas as funcionalidades dos grupos selecionados.</li>
<li>Os grupos <strong>Autor</strong> e <strong>Votante</strong> são gerenciados automaticamente através dos campos "Autor" e "Parlamentar".</li>
</ul>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Fechar</button>
</div>
</div>
</div>
</div>
<!-- Modal de Cadastro Completo de Autor -->
<div class="modal fade" id="modalCadastrarAutor" tabindex="-1" role="dialog" aria-labelledby="modalCadastrarAutorLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" id="modalCadastrarAutorLabel">
<i class="fas fa-user-plus"></i> Cadastrar Novo Autor
</h5>
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Fechar">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form id="formCadastrarAutor">
<!-- Tipo de Autor -->
<div class="form-group">
<label for="tipo_autor_modal">Tipo do Autor *</label>
<select class="form-control" id="tipo_autor_modal" name="tipo_autor" required>
<option value="">---------</option>
</select>
<small class="form-text text-muted">
Selecione o tipo de autor
</small>
</div>
<!-- Campos para Tipo com Content Type (Parlamentar, Comissão, etc) -->
<div id="campos_autor_related" class="d-none">
<div class="form-group">
<label for="autor_related_modal">Selecione o Registro *</label>
<select class="form-control" id="autor_related_modal" name="autor_related">
<option value="">---------</option>
</select>
<small class="form-text text-muted">
Selecione o registro que será vinculado a este autor
</small>
</div>
</div>
<!-- Campos para Tipo Genérico (sem content type) -->
<div id="campos_autor_generico" class="d-none">
<div class="form-group">
<label for="usuario_autor_modal">Selecione o Usuário *</label>
<select class="form-control" id="usuario_autor_modal" name="usuario">
<option value="">Selecione um usuário...</option>
</select>
<small class="form-text text-muted">
Selecione o usuário que será vinculado como autor, ou escolha "Digitar manualmente" para criar um autor sem vínculo com usuário
</small>
</div>
<div id="campo_nome_manual" class="d-none">
<div class="form-group">
<label for="nome_autor_modal">Nome do Autor *</label>
<input type="text" class="form-control" id="nome_autor_modal" name="nome" placeholder="Digite o nome do autor">
<small class="form-text text-muted">
Digite o nome completo do autor
</small>
</div>
</div>
<div class="form-group">
<label for="cargo_autor_modal">Cargo</label>
<input type="text" class="form-control" id="cargo_autor_modal" name="cargo" placeholder="Digite o cargo (opcional)">
<small class="form-text text-muted">
Campo opcional
</small>
</div>
</div>
<div class="alert alert-info mt-3">
<i class="fas fa-info-circle"></i>
<strong>Dica:</strong> Após cadastrar o autor, ele ficará disponível para seleção no campo "Autor" desta página.
</div>
<div class="alert alert-danger d-none" id="erro_modal_autor" role="alert"></div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">
<i class="fas fa-times"></i> Cancelar
</button>
<button type="button" class="btn btn-primary" id="btnSalvarAutor">
<i class="fas fa-save"></i> Salvar Autor
</button>
</div>
</div>
</div>
</div>
{% endblock base_content %}
{% block extra_js %}
<script type="text/javascript">
$(() => {
console.log('==========================================');
console.log('Script user_form.html carregado');
console.log('Modal de ajuda existe:', $('#modalAjudaGrupos').length > 0);
console.log('==========================================');
var $crf_token = $('[name="csrfmiddlewaretoken"]').attr('value');
// Renovar token (funcionalidade existente)
$("#renovar-token").click(() => {
$.ajax({
url: "{% url 'sapl.api:recria_token' object.id %}",
@ -14,6 +336,440 @@
success: (res) => $("#id_token").val(res.token)
});
});
// Adicionar ícone de ajuda ao campo Grupos
function adicionarIconeAjudaGrupos() {
console.log('Tentando adicionar ícone de ajuda...');
// Procurar pelo label do campo "groups" - tentar vários seletores
let gruposLabel = $('label[for="id_groups"]');
if (gruposLabel.length === 0) {
// Tentar encontrar por texto contendo "Grupos"
gruposLabel = $('label.col-form-label').filter(function() {
return $(this).text().trim() === 'Grupos';
});
console.log('Label encontrado por texto:', gruposLabel.length);
} else {
console.log('Label encontrado por for="id_groups":', gruposLabel.length);
}
if (gruposLabel.length > 0 && !$('#btnAjudaGrupos').length) {
// Criar botão de ajuda com espaçamento
const btnAjuda = $('<button type="button" class="btn btn-link btn-sm p-0 ml-2" id="btnAjudaGrupos" title="Clique para ver a descrição de cada grupo" style="vertical-align: middle;">')
.append('<i class="fas fa-question-circle text-info" style="font-size: 1.3em;"></i>');
// Adicionar ao label
gruposLabel.append('&nbsp;').append(btnAjuda);
console.log('✓ Ícone de ajuda adicionado ao campo Grupos');
} else if ($('#btnAjudaGrupos').length) {
console.log('Ícone já existe');
} else {
console.warn('Label Grupos não encontrado. Tentando alternativas...');
// Última tentativa: procurar em todos os labels
$('label').each(function() {
console.log('Label encontrado:', $(this).text().trim(), 'for:', $(this).attr('for'));
});
}
}
// Chamar função após DOM estar pronto - tentar múltiplas vezes
setTimeout(adicionarIconeAjudaGrupos, 100);
setTimeout(adicionarIconeAjudaGrupos, 300);
setTimeout(adicionarIconeAjudaGrupos, 500);
setTimeout(adicionarIconeAjudaGrupos, 1000);
// Observar mudanças no DOM para garantir que o ícone seja adicionado
const observer = new MutationObserver(function(mutations) {
if ($('#btnAjudaGrupos').length === 0) {
adicionarIconeAjudaGrupos();
}
});
// Configurar observador para monitorar o body
observer.observe(document.body, {
childList: true,
subtree: true
});
// Abrir modal de ajuda quando clicar no ícone
$(document).on('click', '#btnAjudaGrupos', function(e) {
e.preventDefault();
console.log('Abrindo modal de ajuda sobre Grupos');
const modal = $('#modalAjudaGrupos');
console.log('Modal encontrado:', modal.length > 0);
// Tentar abrir com Bootstrap 4/5
if (typeof bootstrap !== 'undefined') {
console.log('Usando Bootstrap 5');
const bsModal = new bootstrap.Modal(document.getElementById('modalAjudaGrupos'));
bsModal.show();
} else if ($.fn.modal) {
console.log('Usando Bootstrap 4 (jQuery)');
modal.modal('show');
} else {
console.error('Bootstrap modal não disponível');
// Fallback: mostrar modal manualmente
modal.addClass('show').css('display', 'block');
$('body').addClass('modal-open').append('<div class="modal-backdrop fade show"></div>');
}
});
console.log('Script de ajuda de grupos carregado');
// ===== CADASTRO COMPLETO DE AUTOR VIA MODAL =====
// Variável para armazenar dados dos tipos de autor
let tiposAutorData = {};
// Adicionar botão que abre modal de cadastro
function adicionarBotaoCadastrarAutor() {
console.log('Tentando adicionar botão de cadastro de autor...');
let autorSelect = $('#id_autor');
if (autorSelect.length === 0) {
console.warn('Campo #id_autor não encontrado');
return;
}
if ($('#btnCadastrarAutor').length > 0) {
console.log('Botão já existe');
return;
}
const btnCadastrar = $('<button type="button" class="btn btn-success btn-sm ml-2" id="btnCadastrarAutor" title="Cadastrar novo autor">')
.append('<i class="fas fa-plus"></i> Cadastrar Autor');
autorSelect.after(btnCadastrar);
console.log('✓ Botão de cadastro de autor adicionado');
}
setTimeout(adicionarBotaoCadastrarAutor, 100);
setTimeout(adicionarBotaoCadastrarAutor, 500);
setTimeout(adicionarBotaoCadastrarAutor, 1000);
// Abrir modal ao clicar no botão
$(document).on('click', '#btnCadastrarAutor', function(e) {
e.preventDefault();
console.log('Abrindo modal de cadastro de autor');
// Limpar formulário
$('#formCadastrarAutor')[0].reset();
$('#erro_modal_autor').addClass('d-none').text('');
$('#campos_autor_related').addClass('d-none');
$('#campos_autor_generico').addClass('d-none');
$('#campo_nome_manual').addClass('d-none');
$('#usuario_autor_modal').find('option:not(:first)').remove();
// Carregar tipos de autor
$.ajax({
url: '/api/base/tipoautor/',
type: 'GET',
success: function(response) {
console.log('Tipos de autor carregados:', response.count);
let select = $('#tipo_autor_modal');
select.find('option:not(:first)').remove();
tiposAutorData = {};
response.results.forEach(function(tipo) {
select.append($('<option>', {
value: tipo.id,
text: tipo.descricao
}));
tiposAutorData[tipo.id] = tipo;
});
$('#modalCadastrarAutor').modal('show');
},
error: function(xhr, status, error) {
console.error('Erro ao carregar tipos de autor:', error);
alert('Erro ao carregar tipos de autor. Tente novamente.');
}
});
});
// Ao selecionar tipo de autor, mostrar campos apropriados
$(document).on('change', '#tipo_autor_modal', function() {
let tipoId = $(this).val();
console.log('Tipo de autor selecionado:', tipoId);
$('#campos_autor_related').addClass('d-none');
$('#campos_autor_generico').addClass('d-none');
$('#campo_nome_manual').addClass('d-none');
$('#erro_modal_autor').addClass('d-none');
// Limpar valores dos campos
$('#autor_related_modal').val('');
$('#usuario_autor_modal').val('');
$('#nome_autor_modal').val('');
$('#cargo_autor_modal').val('');
if (!tipoId) {
return;
}
let tipoData = tiposAutorData[tipoId];
if (!tipoData) {
console.error('Dados do tipo não encontrados');
return;
}
console.log('Dados do tipo selecionado:', tipoData);
console.log('Tem content_type?', tipoData.content_type);
// Se tem content_type (Parlamentar, Comissão, etc)
if (tipoData.content_type) {
console.log('Tipo tem content_type (ID):', tipoData.content_type);
console.log('Metadata:', tipoData.metadata);
console.log('Mostrando campos_autor_related, ocultando campos_autor_generico');
$('#campos_autor_related').removeClass('d-none');
$('#campos_autor_generico').addClass('d-none'); // Garantir que está oculto
// Verificar se tem metadata com content_type detalhado
if (tipoData.metadata && tipoData.metadata.content_type) {
console.log('Usando metadata.content_type');
let app = tipoData.metadata.content_type.app_label;
let model = tipoData.metadata.content_type.model;
carregarRegistrosRelacionados(app, model, tipoData.descricao);
} else {
// Tentar buscar via API
$.ajax({
url: `/api/contenttypes/contenttype/${tipoData.content_type}/`,
type: 'GET',
success: function(ctResponse) {
console.log('Content Type detalhado (via API):', ctResponse);
let app = ctResponse.app_label;
let model = ctResponse.model;
carregarRegistrosRelacionados(app, model, tipoData.descricao);
},
error: function() {
$('#erro_modal_autor')
.removeClass('d-none')
.text('Erro ao obter informações do tipo de autor.');
}
});
}
} else {
// Tipo genérico (sem content_type)
console.log('Tipo genérico - mostrando campos_autor_generico, ocultando campos_autor_related');
$('#campos_autor_generico').removeClass('d-none');
$('#campos_autor_related').addClass('d-none'); // Garantir que está oculto
$('#campo_nome_manual').addClass('d-none');
// Carregar usuários disponíveis
carregarUsuariosDisponiveis();
}
});
// Função para carregar registros relacionados
function carregarRegistrosRelacionados(app, model, tipoDescricao) {
$.ajax({
url: `/api/${app}/${model}/`,
type: 'GET',
data: { page_size: 1000 },
success: function(response) {
console.log('Registros carregados:', response.count);
let select = $('#autor_related_modal');
select.find('option:not(:first)').remove();
if (response.results.length === 0) {
$('#erro_modal_autor')
.removeClass('d-none')
.text(`Não há registros de "${tipoDescricao}" cadastrados. Cadastre primeiro um registro deste tipo.`);
return;
}
response.results.forEach(function(registro) {
// Determinar o texto a exibir
let texto = registro.nome || registro.nome_completo || registro.nome_parlamentar || registro.descricao || `ID: ${registro.id}`;
select.append($('<option>', {
value: registro.id,
text: texto
}));
});
},
error: function(xhr, status, error) {
console.error('Erro ao carregar registros:', error);
$('#erro_modal_autor')
.removeClass('d-none')
.text('Erro ao carregar registros disponíveis.');
}
});
}
// Função para carregar usuários disponíveis
function carregarUsuariosDisponiveis() {
$.ajax({
url: '{% url "sapl.base:criar_autor_ajax" %}',
type: 'GET',
success: function(response) {
console.log('Usuários carregados:', response.count);
let select = $('#usuario_autor_modal');
select.find('option:not(:first)').remove();
// Adicionar usuários
response.results.forEach(function(usuario) {
select.append($('<option>', {
value: usuario.id,
text: `${usuario.nome_completo} (${usuario.username})`,
'data-nome': usuario.nome_completo,
'data-username': usuario.username
}));
});
// Adicionar opção para digitar manualmente
select.append($('<option>', {
value: 'manual',
text: '──────────────────────'
}));
select.append($('<option>', {
value: 'manual',
text: '✍ Digitar manualmente...'
}));
},
error: function(xhr, status, error) {
console.error('Erro ao carregar usuários:', error);
$('#erro_modal_autor')
.removeClass('d-none')
.text('Erro ao carregar lista de usuários.');
}
});
}
// Ao selecionar usuário, mostrar/ocultar campo manual
$(document).on('change', '#usuario_autor_modal', function() {
let valor = $(this).val();
if (valor === 'manual') {
// Mostrar campo de texto manual
$('#campo_nome_manual').removeClass('d-none');
$('#nome_autor_modal').val('').focus();
} else {
// Ocultar campo manual
$('#campo_nome_manual').addClass('d-none');
$('#nome_autor_modal').val('');
}
});
// Salvar autor
$(document).on('click', '#btnSalvarAutor', function(e) {
e.preventDefault();
console.log('Salvando novo autor...');
let tipoAutor = $('#tipo_autor_modal').val();
let tipoData = tiposAutorData[tipoAutor];
if (!tipoAutor) {
$('#erro_modal_autor')
.removeClass('d-none')
.text('Por favor, selecione o Tipo de Autor.');
return;
}
let data = {
tipo_autor: tipoAutor
};
// Validar campos conforme o tipo
if (tipoData.content_type) {
// Tem content_type
let autorRelated = $('#autor_related_modal').val();
if (!autorRelated) {
$('#erro_modal_autor')
.removeClass('d-none')
.text('Por favor, selecione um registro.');
return;
}
data.autor_related = autorRelated;
} else {
// Genérico - verificar se selecionou usuário ou digitou manualmente
let usuarioSelecionado = $('#usuario_autor_modal').val();
let nome = '';
if (!usuarioSelecionado) {
$('#erro_modal_autor')
.removeClass('d-none')
.text('Por favor, selecione um usuário ou escolha "Digitar manualmente".');
return;
}
if (usuarioSelecionado === 'manual') {
// Usuario escolheu digitar manualmente
nome = $('#nome_autor_modal').val().trim();
if (!nome) {
$('#erro_modal_autor')
.removeClass('d-none')
.text('Por favor, preencha o Nome do Autor.');
return;
}
} else {
// Usuário selecionou um usuário do sistema
let optionSelecionada = $('#usuario_autor_modal option:selected');
nome = optionSelecionada.data('nome');
if (!nome) {
nome = optionSelecionada.data('username');
}
}
data.nome = nome;
data.cargo = $('#cargo_autor_modal').val().trim();
}
// Desabilitar botão
let btnSalvar = $(this);
btnSalvar.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> Salvando...');
// Enviar requisição
$.ajax({
url: '{% url "sapl.base:criar_autor_ajax" %}',
type: 'POST',
headers: { "X-CSRFToken": $crf_token },
data: data,
success: function(response) {
console.log('Autor criado com sucesso:', response);
// Adicionar ao select principal
let autorSelect = $('#id_autor');
autorSelect.append($('<option>', {
value: response.autor.id,
text: response.autor.nome
}));
autorSelect.val(response.autor.id).trigger('change');
$('#modalCadastrarAutor').modal('hide');
// Mostrar mensagem
alert('Autor "' + response.autor.nome + '" cadastrado com sucesso!');
},
error: function(xhr, status, error) {
console.error('Erro ao criar autor:', xhr.responseJSON);
let errorMsg = 'Erro ao criar autor. Tente novamente.';
if (xhr.responseJSON && xhr.responseJSON.error) {
errorMsg = xhr.responseJSON.error;
}
$('#erro_modal_autor')
.removeClass('d-none')
.text(errorMsg);
},
complete: function() {
btnSalvar.prop('disabled', false).html('<i class="fas fa-save"></i> Salvar Autor');
}
});
});
console.log('Script de cadastro de autor via modal carregado');
});
</script>

166
sapl/templates/fluxo_proposicoes.html

@ -417,6 +417,9 @@
<div style="display: flex; flex-wrap: wrap; gap: 15px; margin-top: 15px;">
<a href="#conceitos" class="btn btn-sm btn-outline-primary">Conceitos</a>
<a href="#atores" class="btn btn-sm btn-outline-primary">Atores</a>
<a href="#permissoes" class="btn btn-sm btn-outline-warning" style="color: white; font-weight: bold;">
<i class="fas fa-key"></i> Permissões Necessárias
</a>
<a href="#estados" class="btn btn-sm btn-outline-primary">Estados</a>
<a href="#fluxo-visual" class="btn btn-sm btn-outline-primary">Fluxo Visual</a>
<a href="#passo-a-passo" class="btn btn-sm btn-outline-primary">Passo a Passo</a>
@ -545,6 +548,111 @@
</ul>
</div>
<!-- Permissões Necessárias -->
<h2 id="permissoes"><i class="fas fa-key"></i> Permissões Necessárias para Criar Proposições</h2>
<div class="concept-card danger">
<h3><i class="fas fa-exclamation-triangle"></i> IMPORTANTE: Requisitos para Acesso</h3>
<p>Para que um usuário possa acessar <code>/proposicao/create</code> e criar proposições, ele precisa atender <strong>DOIS requisitos obrigatórios</strong>:</p>
</div>
<div class="row">
<div class="col-md-6">
<div class="concept-card warning">
<h4><i class="fas fa-users"></i> 1. Grupo "Autor"</h4>
<p>O usuário precisa estar no grupo <strong>"Autor"</strong> do sistema.</p>
<p><strong>Como adicionar:</strong></p>
<div class="step-by-step" style="margin-top: 15px;">
<div class="step-item">
<h4>Ir para Usuários</h4>
<div class="url-box">
<span>URL: <code>/sistema/usuario/</code></span>
<a href="/sistema/usuario/" class="url-link" title="Ir para Lista de Usuários" target="_blank" rel="noopener noreferrer">
<i class="fas fa-external-link-alt"></i> Acessar
</a>
</div>
</div>
<div class="step-item">
<h4>Editar o usuário desejado</h4>
<p>Clique no nome do usuário (ex: helena)</p>
</div>
<div class="step-item">
<h4>Adicionar ao grupo</h4>
<p>Na seção "Grupos", marque <strong>"Autor"</strong></p>
<p>Clique em "Salvar"</p>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="concept-card warning">
<h4><i class="fas fa-link"></i> 2. Vínculo com Autor</h4>
<p>O usuário precisa estar vinculado como <strong>operador de um Autor</strong>.</p>
<p><strong>Como vincular:</strong></p>
<div class="step-by-step" style="margin-top: 15px;">
<div class="step-item">
<h4>Ir para Editar Usuário</h4>
<div class="url-box">
<span>URL: <code>/sistema/usuario/{id}/edit</code></span>
<a href="/sistema/usuario/" class="url-link" title="Ir para Lista de Usuários" target="_blank" rel="noopener noreferrer">
<i class="fas fa-external-link-alt"></i> Ver Lista
</a>
</div>
</div>
<div class="step-item">
<h4>Selecionar o Autor</h4>
<p>No campo <strong>"Autor"</strong>, selecione o autor correspondente (ex: "Helena Silva")</p>
<p>Se não existir autor, crie um parlamentar primeiro</p>
</div>
<div class="step-item">
<h4>Salvar</h4>
<p>Isso cria automaticamente o vínculo OperadorAutor</p>
<div class="concept-card success">
<strong>✓ Pronto!</strong> Agora o usuário pode criar proposições.
</div>
</div>
</div>
</div>
</div>
</div>
<div class="concept-card info">
<h4><i class="fas fa-info-circle"></i> Exemplo de Erro Comum</h4>
<p>Se o usuário <strong>helena</strong> possui os grupos:</p>
<ul>
<li>Operador Geral</li>
<li>Operador de Matéria</li>
<li>Operador de Norma Jurídica</li>
<li>Votante</li>
</ul>
<p><strong>Ela NÃO conseguirá criar proposições</strong> porque:</p>
<ul>
<li>❌ Não está no grupo "Autor"</li>
<li>❌ Não está vinculada a nenhum Autor</li>
</ul>
<p><strong>Solução:</strong> Adicionar ao grupo "Autor" E vincular a um Autor existente.</p>
</div>
<div class="concept-card success">
<h4><i class="fas fa-lightbulb"></i> Atalho Rápido</h4>
<p>Para vincular rapidamente um usuário e permitir criação de proposições:</p>
<ol>
<li>Acesse <a href="/sistema/usuario/" target="_blank" rel="noopener noreferrer"><strong>/sistema/usuario/</strong></a></li>
<li>Clique no usuário desejado</li>
<li>Clique em "Editar"</li>
<li>Marque o grupo <strong>"Autor"</strong></li>
<li>No campo <strong>"Autor"</strong>, selecione o autor correspondente</li>
<li>Salve</li>
</ol>
<div class="url-box">
<span>Ir diretamente para: <code>/sistema/usuario/</code></span>
<a href="/sistema/usuario/" class="url-link" title="Ir para Configurar Usuários" target="_blank" rel="noopener noreferrer">
<i class="fas fa-cog"></i> Configurar Agora
</a>
</div>
</div>
<!-- Estados -->
<h2 id="estados"><i class="fas fa-tasks"></i> Estados de uma Proposição</h2>
@ -825,6 +933,28 @@
<div id="tab2" class="tab-content">
<h3><span class="badge-role badge-parliamentar">PARLAMENTAR</span> Criar e Enviar Proposição</h3>
<div class="concept-card danger">
<h4><i class="fas fa-exclamation-triangle"></i> ATENÇÃO: Verifique os Requisitos Primeiro!</h4>
<p>Antes de tentar criar uma proposição, certifique-se de que o usuário possui:</p>
<ul>
<li>✓ Grupo <strong>"Autor"</strong> atribuído</li>
<li>✓ Vínculo com um <strong>Autor</strong> (parlamentar, comissão, etc.)</li>
</ul>
<p><strong>Se faltar algum requisito, você receberá:</strong></p>
<div style="background: #ffebee; padding: 15px; border-radius: 5px; margin: 10px 0;">
<strong style="color: #c62828;">❌ Acesso Negado</strong><br>
Desculpe, você não tem permissão para acessar esta página ou recurso.
</div>
<p>
<a href="#permissoes" class="btn btn-warning" style="color: white;">
<i class="fas fa-key"></i> Ver Seção de Permissões
</a>
<a href="/sistema/usuario/" class="btn btn-primary" target="_blank" rel="noopener noreferrer">
<i class="fas fa-cog"></i> Configurar Usuário Agora
</a>
</p>
</div>
<div class="step-by-step">
<div class="step-item">
<h4>Login como Parlamentar</h4>
@ -1065,10 +1195,44 @@
<li>Regimes de Tramitação em <code>/sistema/materia/regimetramitacao/</code> <a href="/sistema/materia/regimetramitacao/" class="btn btn-sm btn-outline-primary" style="margin-left: 10px;" target="_blank" rel="noopener noreferrer"><i class="fas fa-arrow-right"></i></a></li>
<li>Parlamentares em <code>/parlamentar/create</code> <a href="/parlamentar/create" class="btn btn-sm btn-outline-primary" style="margin-left: 10px;" target="_blank" rel="noopener noreferrer"><i class="fas fa-arrow-right"></i></a></li>
<li>Usuários em <code>/sistema/usuario/create</code> <a href="/sistema/usuario/create" class="btn btn-sm btn-outline-primary" style="margin-left: 10px;" target="_blank" rel="noopener noreferrer"><i class="fas fa-arrow-right"></i></a></li>
<li>Vincular Usuários a Autores em <code>/sistema/usuario/{id}/edit</code> <a href="/sistema/usuario/" class="btn btn-sm btn-outline-primary" style="margin-left: 10px;" title="Ver lista de usuários" target="_blank" rel="noopener noreferrer"><i class="fas fa-arrow-right"></i></a></li>
</ul>
</div>
<div class="concept-card warning">
<h4><i class="fas fa-exclamation-triangle"></i> CONFIGURAÇÃO CRÍTICA: Permissões para Criar Proposições</h4>
<p><strong>Para cada usuário que precisa criar proposições, você DEVE:</strong></p>
<ol style="margin-top: 15px;">
<li>
<strong>Adicionar ao grupo "Autor"</strong>
<ul>
<li>Acesse <a href="/sistema/usuario/" target="_blank" rel="noopener noreferrer"><code>/sistema/usuario/</code></a></li>
<li>Edite o usuário</li>
<li>Marque o grupo <strong>"Autor"</strong></li>
</ul>
</li>
<li style="margin-top: 10px;">
<strong>Vincular a um Autor</strong>
<ul>
<li>Na mesma tela de edição do usuário</li>
<li>No campo <strong>"Autor"</strong>, selecione o autor correspondente (parlamentar, comissão, etc.)</li>
<li>Salve</li>
</ul>
</li>
</ol>
<div class="url-box" style="margin-top: 15px;">
<span><strong>Configurar agora:</strong> <code>/sistema/usuario/</code></span>
<a href="/sistema/usuario/" class="url-link" title="Ir para Configurar Usuários e Permissões" target="_blank" rel="noopener noreferrer">
<i class="fas fa-cog"></i> Configurar Permissões
</a>
</div>
<p style="margin-top: 15px;"><strong>Sem essas duas configurações, o usuário receberá "Acesso Negado" ao tentar acessar <code>/proposicao/create</code>.</strong></p>
<p>
<a href="#permissoes" class="btn btn-warning" style="color: white;">
<i class="fas fa-key"></i> Ver Detalhes das Permissões
</a>
</p>
</div>
<!-- Resumo -->
<h2><i class="fas fa-clipboard-list"></i> Resumo Executivo</h2>

9
sapl/templates/materia/proposicao_form.html

@ -13,6 +13,13 @@
$("#div_id_texto_original").addClass('hidden');
});
// Adicionar botão ao lado do select de tipo
var $tipoSelect = $("select[name=tipo]");
if ($tipoSelect.length > 0) {
var $btnNovoTipo = $('<a href="/sistema/proposicao/tipo/create" class="btn btn-sm btn-success" style="margin-left: 10px;" title="Cadastrar novo tipo de proposição" target="_blank"><i class="fa fa-plus"></i> Novo Tipo</a>');
$tipoSelect.parent().append($btnNovoTipo);
}
$("select[name=tipo]").change(function(event) {
if (this.selectedOptions[0].getAttribute('data-has-perfil') === "True") {
@ -27,7 +34,7 @@
}
if ($("input[name=tipo_texto]:checked").length == 0) {
$("input[name=tipo_texto]").first().prop('checked', true);
$("input[name=tipo_texto]").first().closest('label').addClass('checked');
$("input[name=tipo_texto]").first().closest('label').addClass('checked');
}
});

349
sapl/templates/materia/tipomaterialegislativa_form.html

@ -0,0 +1,349 @@
{% extends "crud/form.html" %}
{% load i18n %}
{% block extra_css %}
<style>
.legend-with-help {
display: flex;
align-items: center;
gap: 10px;
}
.help-icon {
cursor: pointer;
color: #007bff;
font-size: 18px;
transition: all 0.3s;
}
.help-icon:hover {
color: #0056b3;
transform: scale(1.1);
}
.exemplo-item {
padding: 15px;
margin: 10px 0;
border: 2px solid #e0e0e0;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
background: white;
position: relative;
overflow: hidden;
}
.exemplo-item:hover {
background: #f8f9fa;
border-color: #007bff;
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.2);
transform: translateY(-2px);
}
.exemplo-item:active {
transform: translateY(0) scale(0.98);
box-shadow: 0 2px 6px rgba(0, 123, 255, 0.3);
}
.exemplo-item.clicked {
animation: pulseClick 0.6s ease;
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
border-color: #007bff;
}
@keyframes pulseClick {
0% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(0, 123, 255, 0.7);
}
50% {
transform: scale(1.02);
box-shadow: 0 0 0 10px rgba(0, 123, 255, 0);
}
100% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(0, 123, 255, 0);
}
}
.exemplo-sigla {
font-weight: bold;
color: #007bff;
font-size: 18px;
margin-bottom: 5px;
display: inline-block;
padding: 4px 12px;
background: #e3f2fd;
border-radius: 20px;
transition: all 0.3s;
}
.exemplo-item:hover .exemplo-sigla {
background: #007bff;
color: white;
transform: scale(1.05);
}
.exemplo-descricao {
color: #495057;
font-size: 14px;
}
.modal-header-custom {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 5px 5px 0 0;
}
.exemplos-container {
max-height: 400px;
overflow-y: auto;
}
.categoria-titulo {
font-weight: bold;
color: #495057;
margin-top: 15px;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 2px solid #007bff;
}
@keyframes ripple-effect {
0% {
width: 0;
height: 0;
opacity: 1;
}
100% {
width: 200px;
height: 200px;
opacity: 0;
}
}
.exemplo-item .ripple {
pointer-events: none;
}
/* Adicionar cursor mais visual */
.exemplo-item::before {
content: '👆';
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
font-size: 20px;
opacity: 0;
transition: opacity 0.3s;
}
.exemplo-item:hover::before {
opacity: 0.5;
}
.exemplo-item.clicked::before {
content: '✓';
opacity: 1;
color: #28a745;
font-size: 24px;
animation: checkmark 0.5s ease;
}
@keyframes checkmark {
0% {
transform: translateY(-50%) scale(0);
}
50% {
transform: translateY(-50%) scale(1.3);
}
100% {
transform: translateY(-50%) scale(1);
}
}
</style>
{% endblock %}
{% block extra_js %}
<script type="text/javascript">
$(document).ready(function(){
// Adicionar ícone de ajuda ao lado do legend
const legend = $('legend:contains("Tipo Matéria Legislativa")');
if (legend.length > 0) {
legend.html(`
<div class="legend-with-help">
<span>${legend.text()}</span>
<i class="fas fa-question-circle help-icon" id="help-icon-tipos" title="Ver exemplos de tipos de matéria"></i>
</div>
`);
}
// Exemplos de tipos de matéria legislativa
const exemplos = [
{
categoria: "Proposições Legislativas Principais",
items: [
{ sigla: "PL", descricao: "Projeto de Lei" },
{ sigla: "PLC", descricao: "Projeto de Lei Complementar" },
{ sigla: "PEC", descricao: "Proposta de Emenda à Constituição" },
{ sigla: "PLO", descricao: "Projeto de Lei Orçamentária" },
{ sigla: "PLP", descricao: "Projeto de Lei do Plano Plurianual" }
]
},
{
categoria: "Decretos e Resoluções",
items: [
{ sigla: "DL", descricao: "Decreto Legislativo" },
{ sigla: "RES", descricao: "Resolução" },
{ sigla: "PDL", descricao: "Projeto de Decreto Legislativo" },
{ sigla: "PRS", descricao: "Projeto de Resolução" }
]
},
{
categoria: "Instrumentos de Fiscalização",
items: [
{ sigla: "REQ", descricao: "Requerimento" },
{ sigla: "IND", descricao: "Indicação" },
{ sigla: "MOC", descricao: "Moção" },
{ sigla: "PFC", descricao: "Proposta de Fiscalização e Controle" }
]
},
{
categoria: "Mensagens e Comunicações",
items: [
{ sigla: "MSG", descricao: "Mensagem" },
{ sigla: "OFI", descricao: "Ofício" },
{ sigla: "PAR", descricao: "Parecer" }
]
},
{
categoria: "Emendas",
items: [
{ sigla: "EMC", descricao: "Emenda Constitucional" },
{ sigla: "EMD", descricao: "Emenda" },
{ sigla: "SUB", descricao: "Substitutivo" }
]
}
];
// Criar HTML do modal
let modalHtml = `
<div class="modal fade" id="modal-exemplos-tipos" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header modal-header-custom">
<h4 class="modal-title">
<i class="fas fa-lightbulb"></i> Exemplos de Tipos de Matéria Legislativa
</h4>
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Fechar">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p class="text-muted mb-3">
<i class="fas fa-info-circle"></i> Clique em qualquer exemplo abaixo para preencher automaticamente os campos do formulário.
</p>
<div class="exemplos-container">
`;
// Adicionar categorias e exemplos
exemplos.forEach(categoria => {
modalHtml += `<div class="categoria-titulo">${categoria.categoria}</div>`;
categoria.items.forEach(item => {
modalHtml += `
<div class="exemplo-item" data-sigla="${item.sigla}" data-descricao="${item.descricao}">
<div class="exemplo-sigla">${item.sigla}</div>
<div class="exemplo-descricao">${item.descricao}</div>
</div>
`;
});
});
modalHtml += `
</div>
<div class="alert alert-info mt-3" style="font-size: 13px;">
<i class="fas fa-hand-pointer"></i> <strong>Dica:</strong> Após selecionar um exemplo, você ainda pode editar os campos antes de salvar.
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Fechar</button>
</div>
</div>
</div>
</div>
`;
// Adicionar modal ao body
$('body').append(modalHtml);
// Evento para abrir o modal
$(document).on('click', '#help-icon-tipos', function() {
$('#modal-exemplos-tipos').modal('show');
});
// Evento para preencher os campos ao clicar em um exemplo
$(document).on('click', '.exemplo-item', function() {
const $this = $(this);
const sigla = $this.data('sigla');
const descricao = $this.data('descricao');
// Adicionar classe de clique com animação
$this.addClass('clicked');
// Criar efeito ripple
const ripple = $('<span class="ripple"></span>');
const x = event.pageX - $this.offset().left;
const y = event.pageY - $this.offset().top;
ripple.css({
position: 'absolute',
top: y + 'px',
left: x + 'px',
width: '0',
height: '0',
borderRadius: '50%',
background: 'rgba(0, 123, 255, 0.5)',
transform: 'translate(-50%, -50%)',
animation: 'ripple-effect 0.6s ease-out'
});
$this.append(ripple);
setTimeout(function() {
ripple.remove();
$this.removeClass('clicked');
}, 600);
// Preencher os campos
$('#id_sigla').val(sigla).focus();
$('#id_descricao').val(descricao);
// Adicionar efeito visual nos campos preenchidos
$('#id_sigla, #id_descricao').css('background-color', '#d4edda');
setTimeout(function() {
$('#id_sigla, #id_descricao').css('background-color', '');
}, 1500);
// Fechar o modal após um pequeno delay para mostrar a animação
setTimeout(function() {
$('#modal-exemplos-tipos').modal('hide');
}, 300);
// Mostrar mensagem de sucesso
const mensagem = `
<div class="alert alert-success alert-dismissible fade show" role="alert" style="position: fixed; top: 80px; right: 20px; z-index: 9999; min-width: 300px;">
<i class="fas fa-check-circle"></i> <strong>Campos preenchidos!</strong><br>
<small>Sigla: ${sigla} | Descrição: ${descricao}</small>
<button type="button" class="close" data-dismiss="alert" aria-label="Fechar">
<span aria-hidden="true">&times;</span>
</button>
</div>
`;
$('body').append(mensagem);
setTimeout(function() {
$('.alert-success').fadeOut(function() { $(this).remove(); });
}, 3000);
});
});
</script>
{% endblock %}

627
sapl/templates/materia/tipoproposicao_form.html

@ -1,39 +1,562 @@
{% extends "crud/form.html" %}
{% load i18n %}
{% block extra_css %}
<style>
.legend-with-help {
display: flex;
align-items: center;
gap: 10px;
}
.help-icon {
cursor: pointer;
color: #17a2b8;
font-size: 18px;
transition: all 0.3s;
}
.help-icon:hover {
color: #138496;
transform: scale(1.1);
}
.modal-header-help {
background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
color: white;
border-radius: 5px 5px 0 0;
}
.help-section {
margin-bottom: 25px;
padding: 20px;
border-radius: 8px;
background: #f8f9fa;
border-left: 4px solid #17a2b8;
}
.help-section h5 {
color: #17a2b8;
font-weight: bold;
margin-bottom: 15px;
font-size: 16px;
}
.help-section p {
color: #495057;
line-height: 1.6;
margin-bottom: 10px;
}
.help-example {
background: white;
padding: 12px 15px;
border-radius: 5px;
border: 1px solid #dee2e6;
margin-top: 10px;
font-family: monospace;
color: #495057;
}
.help-step {
display: flex;
align-items: flex-start;
margin-bottom: 15px;
padding: 10px;
background: white;
border-radius: 5px;
}
.help-step-number {
background: #17a2b8;
color: white;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin-right: 15px;
flex-shrink: 0;
}
.help-step-content {
flex: 1;
}
.help-warning {
background: #fff3cd;
border-left: 4px solid #ffc107;
padding: 15px;
border-radius: 5px;
margin-top: 15px;
}
.help-warning strong {
color: #856404;
}
.help-info {
background: #d1ecf1;
border-left: 4px solid #17a2b8;
padding: 15px;
border-radius: 5px;
margin-top: 15px;
}
.help-info strong {
color: #0c5460;
}
.label-with-shortcut {
display: flex;
align-items: center;
gap: 10px;
}
.btn-create-type {
padding: 6px 14px;
font-size: 12px;
font-weight: 600;
border-radius: 5px;
text-decoration: none;
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
color: white;
border: none;
transition: all 0.3s;
display: inline-flex;
align-items: center;
gap: 6px;
white-space: nowrap;
box-shadow: 0 2px 4px rgba(40, 167, 69, 0.2);
position: relative;
overflow: hidden;
}
.btn-create-type::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.2);
transition: left 0.5s;
}
.btn-create-type:hover::before {
left: 100%;
}
.btn-create-type:hover {
background: linear-gradient(135deg, #218838 0%, #1aa179 100%);
color: white;
text-decoration: none;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(40, 167, 69, 0.4);
}
.btn-create-type:active {
transform: translateY(0);
box-shadow: 0 2px 4px rgba(40, 167, 69, 0.3);
}
.btn-create-type i {
font-size: 12px;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}
.btn-create-type:hover i {
animation: spin 0.5s ease;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(180deg);
}
}
#btn-create-type-shortcut {
display: none;
}
/* Badge de "Novo" animado */
.btn-create-type .new-badge {
position: absolute;
top: -5px;
right: -5px;
background: #dc3545;
color: white;
font-size: 8px;
padding: 2px 4px;
border-radius: 3px;
font-weight: bold;
animation: bounce 1s infinite;
}
@keyframes bounce {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-3px);
}
}
</style>
{% endblock %}
{% block extra_js %}
<!-- HACK: incluir uma div modal em um form de uma tela CRUD automática -->
<div id="dialog" title="Dados ausentes" style="display: none">
<p>Não existem tipos vinculados para a definição de tipos informada.
<strong>Isto não permitirá salvar registros nessa tela.</strong></br>
Acesse a tela Parâmetros presente no submenu Sistema.
Em seguida procure a seção entitulada "Matérias Legislativas".
Nesta seção terão dois links: TIPO DE MATÉRIA LEGISLATIVA e TIPO DE DOCUMENTO.
Cadastre os tipos antes de retornar a esta tela.
<div style="margin-bottom: 15px;">
<i class="fas fa-exclamation-triangle" style="color: #f39c12; font-size: 24px; margin-right: 10px;"></i>
<strong style="font-size: 16px;">Nenhum tipo cadastrado!</strong>
</div>
<p style="margin-bottom: 20px;">
Não existem tipos vinculados para a definição selecionada.
<strong>Isto não permitirá salvar registros nessa tela.</strong>
</p>
<p style="margin-bottom: 15px;">
Para continuar, você precisa cadastrar pelo menos um tipo. Use os atalhos abaixo:
</p>
<div id="dialog-links" style="margin-top: 20px; padding: 15px; background-color: #f8f9fa; border-radius: 5px; border-left: 4px solid #007bff;">
<!-- Links serão inseridos dinamicamente aqui -->
</div>
<p style="margin-top: 15px; font-size: 12px; color: #6c757d;">
<i class="fas fa-info-circle"></i> Após cadastrar os tipos necessários, volte a esta página e recarregue para vê-los disponíveis.
</p>
</div>
<script type="text/javascript">
$(document).ready(function(){
// Adicionar ícone de ajuda ao lado do legend
const legend = $('legend:contains("Tipo de Proposição"), legend:contains("Tipo Proposição")');
if (legend.length > 0) {
legend.html(`
<div class="legend-with-help">
<span>${legend.text()}</span>
<i class="fas fa-question-circle help-icon" id="help-icon-proposicao" title="Como preencher esta tela"></i>
</div>
`);
}
// Criar modal de ajuda
const modalHelpHtml = `
<div class="modal fade" id="modal-help-proposicao" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header modal-header-help">
<h4 class="modal-title">
<i class="fas fa-info-circle"></i> Como Cadastrar um Tipo de Proposição
</h4>
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Fechar">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" style="max-height: 500px; overflow-y: auto;">
<div class="help-info">
<strong><i class="fas fa-lightbulb"></i> O que é um Tipo de Proposição?</strong>
<p style="margin-top: 10px; margin-bottom: 0;">
Um Tipo de Proposição define como os autores (parlamentares, comissões, etc.) podem criar propostas
que serão transformadas em Matérias Legislativas ou Documentos.
</p>
</div>
<div class="help-section" style="margin-top: 25px;">
<h5><i class="fas fa-list-ol"></i> Passo a Passo para Cadastrar</h5>
<div class="help-step">
<div class="help-step-number">1</div>
<div class="help-step-content">
<strong>Descrição</strong>
<p>Informe um nome claro e descritivo para o tipo de proposição.</p>
<div class="help-example">
<strong>Exemplos:</strong><br>
• Projeto de Lei<br>
• Emenda ao Projeto<br>
• Requerimento de Informação<br>
• Indicação
</div>
</div>
</div>
<div class="help-step">
<div class="help-step-number">2</div>
<div class="help-step-content">
<strong>Tipo de Conteúdo</strong>
<p>Selecione o tipo de objeto que será criado quando a proposição for incorporada:</p>
<div class="help-example">
<strong>• Tipo de Matéria Legislativa:</strong> Para proposições que virarão matérias (PLs, PECs, etc.)<br>
<strong>• Tipo de Documento:</strong> Para documentos anexos ou complementares
</div>
</div>
</div>
<div class="help-step">
<div class="help-step-number">3</div>
<div class="help-step-content">
<strong>Seleção de Tipo</strong>
<p>Após selecionar o "Tipo de Conteúdo", escolha qual tipo específico será usado.</p>
<div class="help-example">
Se escolheu "Tipo de Matéria Legislativa", selecione qual tipo (ex: Projeto de Lei, Resolução, etc.)
</div>
<div class="help-warning">
<i class="fas fa-exclamation-triangle"></i>
<strong>Atenção:</strong> Se não aparecer nenhuma opção, você precisa cadastrar primeiro
os tipos correspondentes. O sistema mostrará um aviso com links diretos para cadastro.
</div>
</div>
</div>
<div class="help-step">
<div class="help-step-number">4</div>
<div class="help-step-content">
<strong>Perfis (Opcional)</strong>
<p>Selecione quais perfis de autores podem criar proposições deste tipo.</p>
<div class="help-example">
<strong>Exemplo:</strong> Se marcar "Parlamentar", apenas parlamentares poderão criar
proposições deste tipo. Se não marcar nada, todos os perfis poderão usar.
</div>
</div>
</div>
</div>
<div class="help-section">
<h5><i class="fas fa-lightbulb"></i> Exemplo Prático</h5>
<p><strong>Cenário:</strong> Você quer permitir que parlamentares criem Projetos de Lei</p>
<div style="background: white; padding: 15px; border-radius: 5px; border: 1px solid #dee2e6; margin-top: 10px;">
<strong>1. Descrição:</strong> Projeto de Lei<br>
<strong>2. Tipo de Conteúdo:</strong> Tipo de Matéria Legislativa<br>
<strong>3. Seleção de Tipo:</strong> Projeto de Lei (da lista)<br>
<strong>4. Perfis:</strong> ☑ Parlamentar
</div>
<div class="help-info" style="margin-top: 15px;">
<i class="fas fa-check-circle"></i>
<strong>Resultado:</strong> Parlamentares poderão criar proposições do tipo "Projeto de Lei"
que, ao serem incorporadas, virarão Matérias Legislativas do tipo "Projeto de Lei".
</div>
</div>
<div class="help-warning">
<i class="fas fa-info-circle"></i>
<strong>Dica:</strong> O fluxo completo é: Autor cria Proposição → Proposição é recebida →
Proposição é incorporada → Vira Matéria Legislativa ou Documento
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">
<i class="fas fa-times"></i> Fechar
</button>
</div>
</div>
</div>
</div>
`;
// Adicionar modal ao body
$('body').append(modalHelpHtml);
// Evento para abrir o modal de ajuda
$(document).on('click', '#help-icon-proposicao', function() {
$('#modal-help-proposicao').modal('show');
});
// Adicionar botão de atalho ao lado do label "Seleção de Tipo"
// Tentar múltiplos seletores para encontrar o label correto
let labelSelecaoTipo = $('label:contains("Seleção de Tipo")').filter(function() {
return $(this).text().trim() === 'Seleção de Tipo';
}).first();
// Se não encontrar, tentar pelo ID do campo
if (labelSelecaoTipo.length === 0) {
labelSelecaoTipo = $('label[for^="id_tipo_conteudo_related"]').first();
}
// Se não encontrar, tentar pelo container do campo de radio
if (labelSelecaoTipo.length === 0) {
const radioContainer = $('#div_id_tipo_conteudo_related_radio');
if (radioContainer.length > 0) {
labelSelecaoTipo = radioContainer.find('label').first();
}
}
// Se ainda não encontrou, procurar pelo div que contém o campo
if (labelSelecaoTipo.length === 0) {
const divContainer = $('div[id*="tipo_conteudo_related"]').find('label').first();
if (divContainer.length > 0) {
labelSelecaoTipo = divContainer;
}
}
console.log('Label encontrado:', labelSelecaoTipo.length > 0 ? 'Sim' : 'Não', labelSelecaoTipo.text());
if (labelSelecaoTipo.length > 0 && !labelSelecaoTipo.parent().hasClass('label-with-shortcut')) {
labelSelecaoTipo.wrap('<div class="label-with-shortcut"></div>');
labelSelecaoTipo.after(`
<a href="#" id="btn-create-type-shortcut" class="btn-create-type" target="_blank" title="Criar novo tipo em uma nova aba">
<i class="fas fa-plus-circle"></i>
<span id="btn-create-type-text">Criar Tipo</span>
</a>
`);
console.log('Botão de atalho adicionado com sucesso');
} else {
console.log('Não foi possível adicionar o botão - label não encontrado ou já existe');
// Fallback: adicionar após o container do select de content_type
setTimeout(function() {
const contentTypeContainer = $('#div_id_content_type');
if (contentTypeContainer.length > 0 && $('#btn-create-type-shortcut').length === 0) {
const radioDiv = $('#div_id_tipo_conteudo_related_radio');
if (radioDiv.length > 0) {
const existingLabel = radioDiv.find('label').first();
if (existingLabel.length > 0 && !existingLabel.parent().hasClass('label-with-shortcut')) {
existingLabel.wrap('<div class="label-with-shortcut"></div>');
existingLabel.after(`
<a href="#" id="btn-create-type-shortcut" class="btn-create-type" target="_blank" title="Criar novo tipo em uma nova aba">
<i class="fas fa-plus-circle"></i>
<span id="btn-create-type-text">Criar Tipo</span>
</a>
`);
console.log('Botão adicionado via fallback');
}
}
}
}, 500);
}
// Adicionar evento de clique no botão para mostrar notificação
$(document).on('click', '#btn-create-type-shortcut', function(e) {
const tipoCriando = $('#btn-create-type-text').text();
// Mostrar toast de confirmação
const toast = $(`
<div class="alert alert-info alert-dismissible fade show"
style="position: fixed; top: 80px; right: 20px; z-index: 9999; min-width: 320px; box-shadow: 0 4px 12px rgba(0,0,0,0.15);">
<button type="button" class="close" data-dismiss="alert" aria-label="Fechar">
<span aria-hidden="true">&times;</span>
</button>
<strong><i class="fas fa-external-link-alt"></i> Abrindo nova aba...</strong><br>
<small>Criando: ${tipoCriando}</small><br>
<small class="text-muted">💡 Após criar o tipo, volte a esta página e atualize a lista.</small>
</div>
`);
$('body').append(toast);
setTimeout(function() {
toast.fadeOut(function() { $(this).remove(); });
}, 5000);
});
// Função para atualizar o botão de atalho
function updateCreateTypeButton(contentType) {
const typeLinks = {
'materia/tipomaterialegislativa': {
url: '/sistema/materia/tipo/create',
label: 'Criar Tipo de Matéria',
icon: 'fa-file-alt',
tooltip: 'Abrir tela de criação de Tipo de Matéria Legislativa em nova aba'
},
'materia/tipodocumento': {
url: '/sistema/materia/tipo-documento/create',
label: 'Criar Tipo de Documento',
icon: 'fa-file',
tooltip: 'Abrir tela de criação de Tipo de Documento em nova aba'
}
};
if (contentType && typeLinks[contentType]) {
const link = typeLinks[contentType];
$('#btn-create-type-shortcut')
.attr('href', link.url)
.attr('title', link.tooltip)
.fadeIn(300);
$('#btn-create-type-text').text(link.label);
$('#btn-create-type-shortcut i').attr('class', `fas ${link.icon}`);
} else {
$('#btn-create-type-shortcut').fadeOut(200);
}
}
var initial_select = parseInt($("input[name=tipo_conteudo_related]").val());
$("input[name=tipo_conteudo_related]").remove();
$('#id_content_type').change(function(event) {
const url_part_pattern = this[event.target.selectedIndex].value
const url = `/api/${url_part_pattern}/?o=id&page=1&page_size=100`
// Atualizar botão de atalho
updateCreateTypeButton(url_part_pattern);
const rad_tip_conteudo = $("#div_id_tipo_conteudo_related_radio > div").html('');
const popule_rad_tip_conteudo = function(url_get) {
$.get(url_get).done(function(response) {
if (response.pagination.total_entries === 0) {
// Detectar o tipo selecionado e criar links apropriados
const contentType = url_part_pattern;
let dialogLinksHtml = '';
// Mapeamento de content types para URLs de criação
const typeLinks = {
'materia/tipomaterialegislativa': {
url: '/sistema/materia/tipo/create',
label: 'Criar Tipo de Matéria Legislativa',
icon: 'fa-file-alt'
},
'materia/tipodocumento': {
url: '/sistema/materia/tipo-documento/create',
label: 'Criar Tipo de Documento',
icon: 'fa-file'
}
};
if (typeLinks[contentType]) {
const link = typeLinks[contentType];
dialogLinksHtml = `
<div style="text-align: center;">
<a href="${link.url}" target="_blank" class="btn btn-primary" style="padding: 10px 20px; text-decoration: none; display: inline-block;">
<i class="fas ${link.icon}" style="margin-right: 8px;"></i>
${link.label}
</a>
</div>
<p style="margin-top: 10px; text-align: center; font-size: 13px; color: #495057;">
<i class="fas fa-external-link-alt"></i> O link será aberto em uma nova aba
</p>
`;
} else {
dialogLinksHtml = `
<p style="text-align: center; color: #6c757d;">
<i class="fas fa-cog"></i> Acesse a tela de <strong>Parâmetros</strong> no menu <strong>Sistema</strong> para cadastrar os tipos necessários.
</p>
`;
}
$('#dialog-links').html(dialogLinksHtml);
$( "#dialog" ).dialog({
resizable: false,
height: "auto",
width: 400,
width: 550,
modal: true,
buttons: {
OK: function() {
"Fechar": function() {
$( this ).dialog( "close" );
}
}
@ -58,10 +581,98 @@ $(document).ready(function(){
})
if(response.pagination.links.next !== null )
popule_rad_tip_conteudo(response.pagination.links.next)
else {
// Após popular todos os radio buttons, adicionar o botão se ainda não existir
setTimeout(function() {
if ($('#btn-create-type-shortcut').length === 0) {
const $radioFieldDiv = $('#div_id_tipo_conteudo_related_radio');
let $targetLabel = $radioFieldDiv.find('label.col-form-label').first();
if ($targetLabel.length === 0) {
$targetLabel = $radioFieldDiv.find('label').first();
}
if ($targetLabel.length > 0 && !$targetLabel.parent().hasClass('label-with-shortcut')) {
$targetLabel.wrap('<div class="label-with-shortcut"></div>');
$targetLabel.after(`
<a href="#" id="btn-create-type-shortcut" class="btn-create-type" target="_blank" title="Criar novo tipo em uma nova aba">
<i class="fas fa-plus-circle"></i>
<span id="btn-create-type-text">Criar Tipo</span>
</a>
`);
console.log('✓ Botão adicionado após popular radio buttons');
// Atualizar o botão com o tipo atual
const currentType = $('#id_content_type').val();
if (currentType) {
updateCreateTypeButton(currentType);
}
}
}
}, 200);
}
})
}
popule_rad_tip_conteudo(url)
})
// Inicializar o botão de atalho com o valor inicial
const initialContentType = $('#id_content_type').val();
if (initialContentType) {
updateCreateTypeButton(initialContentType);
}
// Garantir que o botão seja adicionado após o DOM estar completamente carregado
setTimeout(function() {
if ($('#btn-create-type-shortcut').length === 0) {
console.log('Tentando adicionar botão via método alternativo...');
// Procurar pelo div do campo tipo_conteudo_related_radio
const $radioFieldDiv = $('#div_id_tipo_conteudo_related_radio');
if ($radioFieldDiv.length > 0) {
console.log('Div do campo encontrado');
// Procurar pelo label dentro deste div
let $targetLabel = $radioFieldDiv.find('label.col-form-label').first();
if ($targetLabel.length === 0) {
$targetLabel = $radioFieldDiv.find('label').first();
}
console.log('Label dentro do div:', $targetLabel.length > 0 ? 'encontrado' : 'não encontrado');
console.log('Texto do label:', $targetLabel.text());
if ($targetLabel.length > 0) {
// Verificar se já não foi aplicado
if (!$targetLabel.parent().hasClass('label-with-shortcut')) {
$targetLabel.wrap('<div class="label-with-shortcut"></div>');
$targetLabel.after(`
<a href="#" id="btn-create-type-shortcut" class="btn-create-type" target="_blank" title="Criar novo tipo em uma nova aba">
<i class="fas fa-plus-circle"></i>
<span id="btn-create-type-text">Criar Tipo</span>
</a>
`);
console.log('✓ Botão de atalho adicionado com sucesso (método alternativo)!');
// Atualizar o botão com o tipo atual
const currentType = $('#id_content_type').val();
if (currentType) {
updateCreateTypeButton(currentType);
}
}
} else {
console.log('⚠ Label não encontrado no div do campo');
}
} else {
console.log('⚠ Div do campo não encontrado');
console.log('Divs disponíveis com "tipo_conteudo":', $('div[id*="tipo_conteudo"]').length);
}
} else {
console.log('✓ Botão já foi adicionado anteriormente');
}
}, 1000);
$('#id_content_type').trigger('change')
})
</script>

465
sapl/templates/parlamentares/vincular_parlamentar.html

@ -3,6 +3,465 @@
{% load crispy_forms_tags %}
{% load common_tags %}
{% block detail_content %}
{% crispy form %}
{% endblock detail_content %}
{% block base_content %}
{% crispy form %}
<!-- Modal para criar Parlamentar -->
<div class="modal fade" id="modalCriarParlamentar" tabindex="-1" role="dialog" aria-labelledby="modalCriarParlamentarLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" id="modalCriarParlamentarLabel">
<i class="fas fa-user-plus"></i> Criar Novo Parlamentar
</h5>
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Fechar">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form id="formCriarParlamentar">
{% csrf_token %}
<div class="row">
<div class="col-md-8">
<div class="form-group">
<label for="parlamentar_nome_completo">Nome Completo <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="parlamentar_nome_completo" name="nome_completo" required>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label for="parlamentar_sexo">Sexo <span class="text-danger">*</span></label>
<select class="form-control" id="parlamentar_sexo" name="sexo" required>
<option value="">Selecione</option>
<option value="F">Feminino</option>
<option value="M">Masculino</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label for="parlamentar_nome_parlamentar">Nome Parlamentar <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="parlamentar_nome_parlamentar" name="nome_parlamentar" required>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="parlamentar_cpf">CPF</label>
<input type="text" class="form-control" id="parlamentar_cpf" name="cpf" placeholder="000.000.000-00">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="parlamentar_rg">RG</label>
<input type="text" class="form-control" id="parlamentar_rg" name="rg">
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label for="parlamentar_data_nascimento">Data de Nascimento</label>
<input type="text" class="form-control dateinput" id="parlamentar_data_nascimento" name="data_nascimento" placeholder="DD/MM/AAAA">
</div>
</div>
<div class="col-md-8">
<div class="form-group">
<label for="parlamentar_email">E-mail</label>
<input type="email" class="form-control" id="parlamentar_email" name="email" placeholder="parlamentar@exemplo.com">
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="parlamentar_telefone">Telefone</label>
<input type="text" class="form-control" id="parlamentar_telefone" name="telefone" placeholder="(00) 0000-0000">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="parlamentar_telefone_celular">Telefone Celular</label>
<input type="text" class="form-control" id="parlamentar_telefone_celular" name="telefone_celular" placeholder="(00) 00000-0000">
</div>
</div>
</div>
<div id="parlamentar-error-message" class="alert alert-danger" style="display: none;"></div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancelar</button>
<button type="button" class="btn btn-primary" id="btnSalvarParlamentar">
<i class="fas fa-save"></i> Salvar
</button>
</div>
</div>
</div>
</div>
<!-- Modal para criar Legislatura -->
<div class="modal fade" id="modalCriarLegislatura" tabindex="-1" role="dialog" aria-labelledby="modalCriarLegislaturaLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title" id="modalCriarLegislaturaLabel">
<i class="fas fa-calendar-plus"></i> Criar Nova Legislatura
</h5>
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Fechar">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form id="formCriarLegislatura">
{% csrf_token %}
<div class="form-group">
<label for="legislatura_numero">Número <span class="text-danger">*</span></label>
<input type="number" class="form-control" id="legislatura_numero" name="numero" min="1" required>
</div>
<div class="form-group">
<label for="legislatura_data_eleicao">Data Eleição <span class="text-danger">*</span></label>
<input type="text" class="form-control dateinput" id="legislatura_data_eleicao" name="data_eleicao" placeholder="DD/MM/AAAA" required>
</div>
<div class="form-group">
<label for="legislatura_data_inicio">Data Início <span class="text-danger">*</span></label>
<input type="text" class="form-control dateinput" id="legislatura_data_inicio" name="data_inicio" placeholder="DD/MM/AAAA" required>
</div>
<div class="form-group">
<label for="legislatura_data_fim">Data Fim <span class="text-danger">*</span></label>
<input type="text" class="form-control dateinput" id="legislatura_data_fim" name="data_fim" placeholder="DD/MM/AAAA" required>
</div>
<div id="legislatura-error-message" class="alert alert-danger" style="display: none;"></div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancelar</button>
<button type="button" class="btn btn-primary" id="btnSalvarLegislatura">
<i class="fas fa-save"></i> Salvar
</button>
</div>
</div>
</div>
</div>
{% endblock base_content %}
{% block extra_js %}
<script type="text/javascript">
$(document).ready(function() {
// Debug: Verificar bibliotecas disponíveis
console.log('jQuery disponível:', typeof $ !== 'undefined');
console.log('Bootstrap 5 disponível:', typeof bootstrap !== 'undefined');
console.log('Bootstrap 4 modal (jQuery):', typeof $.fn.modal !== 'undefined');
// Função para obter CSRF token
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
// Adicionar botões de atalho aos selects
function adicionarBotaoAtalho() {
// Botão para Parlamentar
const parlamentarWrapper = $('#div_id_parlamentar');
if (parlamentarWrapper.length && !$('#btnAbrirModalParlamentar').length) {
const btnParlamentar = $('<button type="button" class="btn btn-sm btn-outline-primary mt-2" id="btnAbrirModalParlamentar"><i class="fas fa-plus"></i> Criar Novo Parlamentar</button>');
parlamentarWrapper.append(btnParlamentar);
console.log('Botão Parlamentar adicionado');
}
// Botão para Legislatura
const legislaturaWrapper = $('#div_id_legislatura');
if (legislaturaWrapper.length && !$('#btnAbrirModalLegislatura').length) {
const btnLegislatura = $('<button type="button" class="btn btn-sm btn-outline-primary mt-2" id="btnAbrirModalLegislatura"><i class="fas fa-plus"></i> Criar Nova Legislatura</button>');
legislaturaWrapper.append(btnLegislatura);
console.log('Botão Legislatura adicionado');
}
}
// Chamar função para adicionar botões após DOM estar pronto
setTimeout(adicionarBotaoAtalho, 100);
// Abrir modal de criar Parlamentar
$(document).on('click', '#btnAbrirModalParlamentar', function(e) {
e.preventDefault();
console.log('Abrindo modal Parlamentar');
// Limpar todos os campos
$('#parlamentar_nome_completo').val('');
$('#parlamentar_nome_parlamentar').val('');
$('#parlamentar_sexo').val('');
$('#parlamentar_cpf').val('');
$('#parlamentar_rg').val('');
$('#parlamentar_data_nascimento').val('');
$('#parlamentar_email').val('');
$('#parlamentar_telefone').val('');
$('#parlamentar_telefone_celular').val('');
$('#parlamentar-error-message').hide();
// Verificar se modal existe
const modal = $('#modalCriarParlamentar');
console.log('Modal Parlamentar encontrado:', modal.length > 0);
// Tentar abrir com Bootstrap 4/5
if (typeof bootstrap !== 'undefined') {
console.log('Usando Bootstrap 5');
const bsModal = new bootstrap.Modal(document.getElementById('modalCriarParlamentar'));
bsModal.show();
} else if ($.fn.modal) {
console.log('Usando Bootstrap 4 (jQuery)');
modal.modal('show');
} else {
console.error('Bootstrap modal não disponível');
// Fallback: mostrar modal manualmente
modal.addClass('show').css('display', 'block');
$('body').addClass('modal-open').append('<div class="modal-backdrop fade show"></div>');
}
});
// Abrir modal de criar Legislatura
$(document).on('click', '#btnAbrirModalLegislatura', function(e) {
e.preventDefault();
console.log('Abrindo modal Legislatura');
// Limpar campos
$('#legislatura_numero').val('');
$('#legislatura_data_eleicao').val('');
$('#legislatura_data_inicio').val('');
$('#legislatura_data_fim').val('');
$('#legislatura-error-message').hide();
// Verificar se modal existe
const modal = $('#modalCriarLegislatura');
console.log('Modal Legislatura encontrado:', modal.length > 0);
// Tentar abrir com Bootstrap 4/5
if (typeof bootstrap !== 'undefined') {
console.log('Usando Bootstrap 5');
const bsModal = new bootstrap.Modal(document.getElementById('modalCriarLegislatura'));
bsModal.show();
} else if ($.fn.modal) {
console.log('Usando Bootstrap 4 (jQuery)');
modal.modal('show');
} else {
console.error('Bootstrap modal não disponível');
// Fallback: mostrar modal manualmente
modal.addClass('show').css('display', 'block');
$('body').addClass('modal-open').append('<div class="modal-backdrop fade show"></div>');
}
});
// Função helper para fechar modal
function fecharModal(modalId) {
const modal = $('#' + modalId);
if (typeof bootstrap !== 'undefined') {
const bsModal = bootstrap.Modal.getInstance(document.getElementById(modalId));
if (bsModal) {
bsModal.hide();
}
} else if ($.fn.modal) {
modal.modal('hide');
} else {
modal.removeClass('show').css('display', 'none');
$('.modal-backdrop').remove();
$('body').removeClass('modal-open');
}
}
// Salvar Parlamentar
$('#btnSalvarParlamentar').click(function() {
const formData = {
nome_completo: $('#parlamentar_nome_completo').val(),
nome_parlamentar: $('#parlamentar_nome_parlamentar').val(),
sexo: $('#parlamentar_sexo').val(),
cpf: $('#parlamentar_cpf').val(),
rg: $('#parlamentar_rg').val(),
data_nascimento: $('#parlamentar_data_nascimento').val(),
email: $('#parlamentar_email').val(),
telefone: $('#parlamentar_telefone').val(),
telefone_celular: $('#parlamentar_telefone_celular').val()
};
console.log('Salvando parlamentar:', formData);
$.ajax({
url: '{% url "sapl.parlamentares:criar_parlamentar_ajax" %}',
type: 'POST',
data: formData,
headers: {
'X-CSRFToken': csrftoken
},
success: function(response) {
console.log('Resposta do servidor:', response);
if (response.success) {
// Adicionar nova opção ao select
const newOption = new Option(
response.parlamentar.nome,
response.parlamentar.id,
true,
true
);
$('#id_parlamentar').append(newOption).trigger('change');
// Fechar modal
fecharModal('modalCriarParlamentar');
// Mostrar mensagem de sucesso
alert('Parlamentar criado com sucesso!');
} else {
$('#parlamentar-error-message').text(response.error).show();
}
},
error: function(xhr, status, error) {
console.error('Erro ao salvar:', error);
$('#parlamentar-error-message').text('Erro ao criar parlamentar. Tente novamente.').show();
}
});
});
// Salvar Legislatura
$('#btnSalvarLegislatura').click(function() {
const formData = {
numero: $('#legislatura_numero').val(),
data_eleicao: $('#legislatura_data_eleicao').val(),
data_inicio: $('#legislatura_data_inicio').val(),
data_fim: $('#legislatura_data_fim').val()
};
console.log('Salvando legislatura:', formData);
$.ajax({
url: '{% url "sapl.parlamentares:criar_legislatura_ajax" %}',
type: 'POST',
data: formData,
headers: {
'X-CSRFToken': csrftoken
},
success: function(response) {
console.log('Resposta do servidor:', response);
if (response.success) {
// Adicionar nova opção ao select
const newOption = new Option(
response.legislatura.nome,
response.legislatura.id,
true,
true
);
$('#id_legislatura').append(newOption).trigger('change');
// Fechar modal
fecharModal('modalCriarLegislatura');
// Mostrar mensagem de sucesso
alert('Legislatura criada com sucesso!');
} else {
$('#legislatura-error-message').text(response.error).show();
}
},
error: function(xhr, status, error) {
console.error('Erro ao salvar:', error);
$('#legislatura-error-message').text('Erro ao criar legislatura. Tente novamente.').show();
}
});
});
// Inicializar datepicker para campos de data (se disponível)
if ($.fn.datepicker) {
$('.dateinput').datepicker({
format: 'dd/mm/yyyy',
language: 'pt-BR',
autoclose: true,
todayHighlight: true
});
} else {
console.log('Datepicker não disponível, usando input nativo');
// Configurar máscara manualmente se datepicker não estiver disponível
$('.dateinput').attr('placeholder', 'DD/MM/AAAA');
}
// Validação de formato de data nos campos
$('.dateinput').on('blur', function() {
const val = $(this).val();
if (val) {
const regex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
if (!regex.test(val)) {
$(this).addClass('is-invalid');
alert('Data inválida. Use o formato DD/MM/AAAA');
} else {
$(this).removeClass('is-invalid');
}
}
});
// Máscaras de input
function aplicarMascaras() {
// Máscara CPF
$('#parlamentar_cpf').on('input', function() {
let val = $(this).val().replace(/\D/g, '');
if (val.length > 11) val = val.substr(0, 11);
if (val.length > 9) {
val = val.replace(/^(\d{3})(\d{3})(\d{3})(\d{2})$/, '$1.$2.$3-$4');
} else if (val.length > 6) {
val = val.replace(/^(\d{3})(\d{3})(\d{1,3})$/, '$1.$2.$3');
} else if (val.length > 3) {
val = val.replace(/^(\d{3})(\d{1,3})$/, '$1.$2');
}
$(this).val(val);
});
// Máscara Telefone
$('#parlamentar_telefone').on('input', function() {
let val = $(this).val().replace(/\D/g, '');
if (val.length > 10) val = val.substr(0, 10);
if (val.length > 6) {
val = val.replace(/^(\d{2})(\d{4})(\d{1,4})$/, '($1) $2-$3');
} else if (val.length > 2) {
val = val.replace(/^(\d{2})(\d{1,4})$/, '($1) $2');
} else if (val.length > 0) {
val = val.replace(/^(\d{1,2})$/, '($1');
}
$(this).val(val);
});
// Máscara Celular
$('#parlamentar_telefone_celular').on('input', function() {
let val = $(this).val().replace(/\D/g, '');
if (val.length > 11) val = val.substr(0, 11);
if (val.length > 7) {
val = val.replace(/^(\d{2})(\d{5})(\d{1,4})$/, '($1) $2-$3');
} else if (val.length > 2) {
val = val.replace(/^(\d{2})(\d{1,5})$/, '($1) $2');
} else if (val.length > 0) {
val = val.replace(/^(\d{1,2})$/, '($1');
}
$(this).val(val);
});
}
// Aplicar máscaras
aplicarMascaras();
console.log('Script de modais carregado com sucesso');
});
</script>
{% endblock extra_js %}
Loading…
Cancel
Save