Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
799f0ec
feat: cria MesaDiretoraCrud e ComposicaoMesaCrud sem forms e regras d…
LeandroJatai Apr 13, 2026
652e74f
feat: cria filterset para seleção de legislatura
LeandroJatai Apr 13, 2026
7acb516
feat: add field titulo
LeandroJatai Apr 13, 2026
2b55d6d
feat: impl tabs na listagem de mesa diretora perfil público
LeandroJatai Apr 13, 2026
b32959c
ajustes no layout publico de mesa
LeandroJatai Apr 13, 2026
106847a
ajustes de scss para telas mobiles
LeandroJatai Apr 14, 2026
08c5eb6
altera link para button nas tabs de mesa diretora
LeandroJatai Apr 14, 2026
2e894ca
add forms com regras de negócio
LeandroJatai Apr 14, 2026
52240ba
add testes para mesa e composição
LeandroJatai Apr 14, 2026
799d0e0
ajustes nos testes e no form mesa diretora
LeandroJatai Apr 14, 2026
958e83f
remove código substituído
LeandroJatai Apr 14, 2026
cfdfd69
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3083470632
LeandroJatai Apr 17, 2026
65e50bc
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3083471759
LeandroJatai Apr 17, 2026
76dec7b
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3083757964
LeandroJatai Apr 17, 2026
7a14c1e
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093425814
LeandroJatai Apr 17, 2026
507cc4a
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3083452572
LeandroJatai Apr 17, 2026
c9e55dc
fix: https://github.com/interlegis/sapl/pull/3829/changes/BASE..958e8…
LeandroJatai Apr 17, 2026
3d62955
fix: reviews
LeandroJatai Apr 17, 2026
a6ee8d3
fix: ajuste em subnav_mesa.yaml
LeandroJatai Apr 17, 2026
29c9dc0
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093455410
LeandroJatai Apr 17, 2026
f36970c
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093434428
LeandroJatai Apr 17, 2026
5faefcb
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093327707
LeandroJatai Apr 17, 2026
fcb2de6
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093438428
LeandroJatai Apr 18, 2026
f777e02
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093737607
LeandroJatai Apr 18, 2026
98e687f
fix: https://github.com/interlegis/sapl/pull/3829#discussion_r3093543652
LeandroJatai Apr 18, 2026
1b144e4
fix: ajuste de related_name
LeandroJatai Apr 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/src/__global/scss/layouts/_globals.scss
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ small {
hyphens: auto;*/
}


@media print {
a[href]:after {
content: none !important;
Expand Down
89 changes: 89 additions & 0 deletions frontend/src/__global/scss/libs/bootstrap/_nav_tabs.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
@import "~bootstrap/scss/variables";

// Estilização do tab-content conectado ao nav-tabs (substitui inline style)
.nav-tabs + .tab-content {
border: 1px solid $nav-tabs-border-color;
border-top: 0;
border-radius: 0 0 $border-radius $border-radius;
}

@media (max-width: 992px) {
.nav-tabs {
position: relative;
flex-direction: column;
border: 1px solid $nav-tabs-border-color;
border-radius: $border-radius; // Totalmente arredondado — parece um botão/select
background-color: $white;
overflow: hidden; // Recorta filhos nas bordas arredondadas

// Seta indicando dropdown
&::after {
content: "▾";
position: absolute;
right: 0.75rem;
top: 0.6rem;
font-size: 1rem;
color: $secondary;
pointer-events: none;
transition: transform 0.2s ease;
}

// Oculta todos os itens por padrão
.nav-item {
display: none;
width: 100%;

.nav-link {
border: none;
border-bottom: 1px solid $nav-tabs-border-color;
border-radius: 0;
width: 100%;
text-align: left;
padding-right: 2rem;
margin-bottom: 0;

&.active {
background-color: $nav-tabs-link-active-bg;
color: $nav-tabs-link-active-color;
border-color: transparent;
}

&:hover:not(.active) {
background-color: $light;
}
}

&:last-child .nav-link {
border-bottom: none;
}
}

// CSS nativo: exibe somente o item ativo (browsers com suporte a :has)
.nav-item:has(.nav-link.active) {
display: block;
}

// Estado expandido: via :focus-within (nativo) ou .nav-tabs--open (fallback JS)
&:focus-within,
&.nav-tabs--open {
border-radius: $border-radius $border-radius 0 0; // Arredonda apenas topo quando aberto
overflow: visible;
z-index: $zindex-dropdown;

&::after {
transform: rotate(180deg);
}

.nav-item {
display: block;
}
}
}

// Em mobile, tab-content é visualmente independente do nav-tabs (que vira um select)
.nav-tabs + .tab-content {
border-top: 1px solid $nav-tabs-border-color;
border-radius: $border-radius; // Rounding completo — desconectado do nav-tabs
margin-top: 0.5rem;
}
}
1 change: 1 addition & 0 deletions frontend/src/__global/scss/libs/libs.scss
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
@import "./bootstrap/nav_navbar";
@import "./bootstrap/nav_tabs";
@import "./bootstrap/table";
54 changes: 50 additions & 4 deletions sapl/parlamentares/forms.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from datetime import timedelta
import logging

from crispy_forms.layout import Fieldset, Layout
from crispy_forms.layout import Fieldset, Layout, Field
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import Field adicionado mas não usado em nenhum lugar do diff. fix_qa.sh (isort/autopep8) deveria limpar.

from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group, User
Expand All @@ -19,10 +19,10 @@
from sapl.crispy_layout_mixin import SaplFormHelper
from sapl.crispy_layout_mixin import form_actions, to_row
from sapl.rules import SAPL_GROUP_VOTANTE
from sapl.utils import FileFieldCheckMixin
from sapl.utils import FileFieldCheckMixin, SelectSubmitChangeWidget

from .models import (Coligacao, ComposicaoColigacao, Filiacao, Frente, Legislatura,
Mandato, Parlamentar, Partido, Votante, Bloco, FrenteParlamentar, BlocoMembro)
from .models import (Coligacao, ComposicaoColigacao, ComposicaoMesa, Filiacao, Frente, Legislatura,
Mandato, MesaDiretora, Parlamentar, Partido, Votante, Bloco, FrenteParlamentar, BlocoMembro)


class CustomImageCropWidget(ImageCropWidget):
Expand Down Expand Up @@ -752,3 +752,49 @@ def clean(self):
_("Parlamentar já é membro do bloco parlamentar."))

return cd

class MesaDiretoraFilterSet(django_filters.FilterSet):

legislatura = django_filters.ModelChoiceFilter(
label='',
queryset=Legislatura.objects.all(),
widget=SelectSubmitChangeWidget)

class Meta:
model = MesaDiretora
fields = ['legislatura']

def __init__(self, *args, **kwargs):
super(MesaDiretoraFilterSet, self).__init__(*args, **kwargs)

row0 = to_row([('legislatura', 5)])

self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Escolha da Legislatura'),
row0,)
)


class MesaDiretoraForm(ModelForm):

class Meta:
model = MesaDiretora
fields = '__all__'


class ComposicaoMesaForm(ModelForm):

class Meta:
model = ComposicaoMesa
fields = (
'parlamentar',
'cargo'
)

def __init__(self, *args, **kwargs):
super(ComposicaoMesaForm, self).__init__(*args, **kwargs)
self.instance.mesa_diretora = self.initial.get('mesa_diretora')
self.fields['parlamentar'].queryset = self.fields['parlamentar'].queryset.filter(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ComposicaoMesaForm.__init__ quebra silenciosamente sem initial

Se o form for instanciado sem initial['mesa_diretora'], self.initial.get(...) retorna None e a próxima linha lança AttributeError: 'NoneType' object has no attribute 'legislatura'.

Hoje as views CRUD sempre passam initial, então não dispara em produção, mas qualquer caller futuro (admin, comando, outra view) quebra silenciosamente. Sugestão de guarda:

mesa = self.initial.get('mesa_diretora')
if mesa is not None:
    self.instance.mesa_diretora = mesa
    self.fields['parlamentar'].queryset = (
        self.fields['parlamentar'].queryset
        .filter(mandato__legislatura=mesa.legislatura)
    )

mandato__legislatura=self.initial.get('mesa_diretora').legislatura)
34 changes: 34 additions & 0 deletions sapl/parlamentares/migrations/0046_mesadiretora_legislatura.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 2.2.28 on 2026-04-13 01:48

from django.db import migrations, models
import django.db.models.deletion
from datetime import date

def add_legislatura_to_mesa_diretora(apps, schema_editor):
schema_editor.execute("""
UPDATE parlamentares_mesadiretora md
SET
legislatura_id = sl.legislatura_id,
data_inicio = sl.data_inicio,
data_fim = sl.data_fim
FROM
parlamentares_sessaolegislativa sl
WHERE
sl.id = md.sessao_legislativa_id
""")


class Migration(migrations.Migration):

dependencies = [
('parlamentares', '0045_auto_20251201_1531'),
]

operations = [
migrations.AddField(
model_name='mesadiretora',
name='legislatura',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='parlamentares.Legislatura', verbose_name='Legislatura'),
),
migrations.RunPython(add_legislatura_to_mesa_diretora),
]
23 changes: 23 additions & 0 deletions sapl/parlamentares/migrations/0047_auto_20260412_2256.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 2.2.28 on 2026-04-13 01:56

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('parlamentares', '0046_mesadiretora_legislatura'),
]

operations = [
migrations.AlterField(
model_name='mesadiretora',
name='legislatura',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='parlamentares.Legislatura', verbose_name='Legislatura'),
),
migrations.RemoveField(
model_name='mesadiretora',
name='sessao_legislativa',
),
]
42 changes: 42 additions & 0 deletions sapl/parlamentares/migrations/0048_auto_20260413_1049.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Generated by Django 2.2.28 on 2026-04-13 13:49

from django.db import migrations, models
import django.db.models.deletion

def preencher_titulo_mesa_diretora(apps, schema_editor):
schema_editor.execute("""
UPDATE parlamentares_mesadiretora
SET titulo = 'Mesa Diretora' ||
CASE WHEN EXTRACT(YEAR FROM data_fim)::integer - EXTRACT(YEAR FROM data_inicio)::integer = 1
THEN ' Biênio'
ELSE ''
END || ' ' ||
EXTRACT(YEAR FROM data_inicio)::integer::text || '/' ||
EXTRACT(YEAR FROM data_fim)::integer::text
WHERE data_inicio IS NOT NULL AND data_fim IS NOT NULL
""")


class Migration(migrations.Migration):

dependencies = [
('parlamentares', '0047_auto_20260412_2256'),
]

operations = [
migrations.AlterModelOptions(
name='mesadiretora',
options={'ordering': ('-legislatura', '-data_inicio'), 'verbose_name': 'Mesa Diretora', 'verbose_name_plural': 'Mesas Diretoras'},
),
migrations.AddField(
model_name='mesadiretora',
name='titulo',
field=models.CharField(default='', max_length=100, verbose_name='Título da Mesa Diretora'),
),
migrations.AlterField(
model_name='composicaomesa',
name='mesa_diretora',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='composicaomesa_set', to='parlamentares.MesaDiretora'),
),
migrations.RunPython(preencher_titulo_mesa_diretora),
]
39 changes: 39 additions & 0 deletions sapl/parlamentares/migrations/0049_auto_20260417_1917.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Generated by Django 2.2.28 on 2026-04-17 22:17

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('parlamentares', '0048_auto_20260413_1049'),
]

operations = [
migrations.AlterField(
model_name='composicaomesa',
name='cargo',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='parlamentares.CargoMesa', verbose_name='Cargo'),
),
migrations.AlterField(
model_name='composicaomesa',
name='mesa_diretora',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='composicaomesa_set', to='parlamentares.MesaDiretora', verbose_name='Mesa Diretora'),
),
migrations.AlterField(
model_name='composicaomesa',
name='parlamentar',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='parlamentares.Parlamentar', verbose_name='Parlamentar'),
),
migrations.AlterField(
model_name='mesadiretora',
name='data_fim',
field=models.DateField(verbose_name='Data Fim'),
),
migrations.AlterField(
model_name='mesadiretora',
name='data_inicio',
field=models.DateField(verbose_name='Data Início'),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Migration pode falhar em bases legadas

O modelo antigo permitia data_inicio/data_fim nulos em MesaDiretora (null=True). Fluxo atual:

  • 0046 popula essas datas a partir de sessao_legislativa, mas só replica os valores que a sessão tinha.
  • 0048 popula titulo apenas onde data_inicio IS NOT NULL AND data_fim IS NOT NULL.
  • 0049 (esta migration) aplica NOT NULL em ambas.

Se houver MesaDiretora cuja sessão não tinha datas (ou o join falhar), 0049 quebra com IntegrityError no upgrade. Como as bases dos clientes são bem heterogêneas, sugiro:

  1. Em 0046 (ou nova migration intermediária), preencher fallback antes do AlterField para NOT NULL — por exemplo, copiar das datas da legislatura quando a sessão não tiver.
  2. Rodar a migration contra alguns dumps reais antes do release.

),
]
19 changes: 19 additions & 0 deletions sapl/parlamentares/migrations/0050_auto_20260417_2235.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 2.2.28 on 2026-04-18 01:35

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('parlamentares', '0049_auto_20260417_1917'),
]

operations = [
migrations.AlterField(
model_name='mesadiretora',
name='legislatura',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='mesadiretora_set', to='parlamentares.Legislatura', verbose_name='Legislatura'),
),
]
Loading
Loading