From a4442eaee4e3aed896df090897403f9789d63582 Mon Sep 17 00:00:00 2001 From: RogerKoala Date: Tue, 16 Jun 2026 11:25:10 -0300 Subject: [PATCH 1/4] =?UTF-8?q?Ajusta=20views=20e=20agrega=C3=A7=C3=A3o=20?= =?UTF-8?q?de=20votos=20na=20migration=20da=20sess=C3=A3o=20plen=C3=A1ria?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../migrations/0070_views_sessao_plenaria.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sapl/sessao/migrations/0070_views_sessao_plenaria.py b/sapl/sessao/migrations/0070_views_sessao_plenaria.py index 74c0be100..4e8791ac5 100644 --- a/sapl/sessao/migrations/0070_views_sessao_plenaria.py +++ b/sapl/sessao/migrations/0070_views_sessao_plenaria.py @@ -186,14 +186,14 @@ class Migration(migrations.Migration): LEFT JOIN LATERAL ( SELECT em.sessao_plenaria_id, em.numero_ordem, - jsonb_agg(jsonb_build_object( + jsonb_object_agg( vp.parlamentar_id, jsonb_build_object( 'materia_id', em.materia_id, 'parlamentar_id', vp.parlamentar_id, 'parlamentar_nome', p.nome_parlamentar, 'voto', vp.voto - )) ORDER BY p.nome_parlamentar) as votos_parlamentares + )) as votos_parlamentares FROM sessao_votoparlamentar vp JOIN parlamentares_parlamentar p ON (vp.parlamentar_id = p.id) WHERE vp.expediente_id = em.id AND em.tipo_votacao != 4 @@ -227,7 +227,7 @@ class Migration(migrations.Migration): FROM sessao_ordemdia od JOIN materia_materialegislativa ml ON (od.materia_id = ml.id) JOIN materia_tipomaterialegislativa tm ON (ml.tipo_id = tm.id) - LEFT JOIN sessao_registroleitura rl on (od.id = rl.expediente_id) + LEFT JOIN sessao_registroleitura rl on (od.id = rl.ordem_id) LEFT JOIN LATERAL ( SELECT jsonb_build_object( 'votos_sim', coalesce(rv.numero_votos_sim, 0), @@ -238,21 +238,21 @@ class Migration(migrations.Migration): trv.nome resultado_votacao FROM sessao_registrovotacao rv JOIN sessao_tiporesultadovotacao trv on (rv.tipo_resultado_votacao_id = trv.id) - WHERE rv.expediente_id = od.id AND tipo_votacao != 4) rv ON TRUE + WHERE rv.ordem_id = od.id AND tipo_votacao != 4) rv ON TRUE LEFT JOIN LATERAL ( SELECT od.sessao_plenaria_id, od.numero_ordem, - jsonb_agg(jsonb_build_object( + jsonb_object_agg( vp.parlamentar_id, jsonb_build_object( 'materia_id', od.materia_id, 'parlamentar_id', vp.parlamentar_id, 'parlamentar_nome', p.nome_parlamentar, 'voto', vp.voto - )) ORDER BY p.nome_parlamentar) as votos_parlamentares + )) as votos_parlamentares FROM sessao_votoparlamentar vp JOIN parlamentares_parlamentar p ON (vp.parlamentar_id = p.id) - WHERE vp.expediente_id = od.id AND od.tipo_votacao != 4 + WHERE vp.ordem_id = od.id AND od.tipo_votacao != 4 GROUP BY od.sessao_plenaria_id, od.numero_ordem ORDER BY od.sessao_plenaria_id, od.numero_ordem ) vp ON TRUE From 13e29437a35ce5424eef4c5130ea4d97faa5c390 Mon Sep 17 00:00:00 2001 From: RogerKoala Date: Tue, 16 Jun 2026 11:30:16 -0300 Subject: [PATCH 2/4] Cria e adiciona componente `footer.vue` ao painel --- .../src/components/painel/PainelFooter.vue | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 frontend/src/components/painel/PainelFooter.vue diff --git a/frontend/src/components/painel/PainelFooter.vue b/frontend/src/components/painel/PainelFooter.vue new file mode 100644 index 000000000..c2ffea383 --- /dev/null +++ b/frontend/src/components/painel/PainelFooter.vue @@ -0,0 +1,62 @@ + + + + + \ No newline at end of file From 9279b86a477f29eb4df1314a6a29979c62e587ec Mon Sep 17 00:00:00 2001 From: RogerKoala Date: Tue, 16 Jun 2026 11:25:20 -0300 Subject: [PATCH 3/4] Adiciona suporte a fotografias de parlamentares nos dados do painel --- sapl/painel/consumers.py | 23 +++++++++++++++++++++++ sapl/painel/views.py | 16 ++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/sapl/painel/consumers.py b/sapl/painel/consumers.py index 0e6f06b97..895a4844e 100644 --- a/sapl/painel/consumers.py +++ b/sapl/painel/consumers.py @@ -65,6 +65,29 @@ def get_dados_painel(sessao_plenaria_id: int) -> dict: 'nome_parlamentar', 'filiacao', ) parlamentares = [dict(zip(['parlamentar_id', 'nome_parlamentar', 'filiacao'], p)) for p in presentes] + + # Adicionar fotografia dos parlamentares + from sapl.parlamentares.models import Parlamentar + from image_cropping.utils import get_backend + parlamentar_ids = [p['parlamentar_id'] for p in parlamentares] + parlamentares_db = {p.id: p for p in Parlamentar.objects.filter(id__in=parlamentar_ids)} + for p in parlamentares: + par = parlamentares_db.get(p['parlamentar_id']) + if par and par.fotografia: + try: + p['fotografia'] = get_backend().get_thumbnail_url( + par.fotografia, + { + 'size': (128, 128), + 'box': par.cropping, + 'crop': True, + 'detail': True, + } + ) + except Exception: + p['fotografia'] = None + else: + p['fotografia'] = None if materia_votacao and materia_votacao.numero_votos: materia_votacao.numero_votos.update({"num_presentes": len(parlamentares)}) diff --git a/sapl/painel/views.py b/sapl/painel/views.py index 53abaa1de..fe227740f 100644 --- a/sapl/painel/views.py +++ b/sapl/painel/views.py @@ -13,6 +13,7 @@ from django.shortcuts import render from django.utils import timezone from django.utils.translation import ugettext_lazy as _ +from sapl import settings from sapl.base.models import AppConfig as ConfiguracoesAplicacao from sapl.base.models import CasaLegislativa from sapl.crud.base import Crud @@ -23,6 +24,7 @@ from sapl.sessao.models import (ExpedienteMateria, OradorExpediente, OrdemDia, SessaoPlenaria, SessaoPlenariaPresenca, VotoParlamentar, RegistroLeitura) from sapl.utils import filiacao_data, get_client_ip, sort_lista_chave +from image_cropping.utils import get_backend from .models import Cronometro @@ -402,10 +404,24 @@ def get_presentes(pk, response, materia): else: partido = filiacao + + if p.parlamentar.fotografia: + thumbnail_url = get_backend().get_thumbnail_url( + p.parlamentar.fotografia, + { + 'size': (128, 128), + 'box': p.parlamentar.cropping, + 'crop': True, + 'detail': True, + } + ) + else: + thumbnail_url = False presentes_list.append( {'id': p.id, 'parlamentar_id': p.parlamentar.id, 'nome': p.parlamentar.nome_parlamentar, + 'fotografia':thumbnail_url, 'partido': partido, 'voto': '' }) From ed29b9b4e2dd10edb0fc231918d8fd930ca2f2bb Mon Sep 17 00:00:00 2001 From: RogerKoala Date: Tue, 16 Jun 2026 11:26:07 -0300 Subject: [PATCH 4/4] Refatora interface do painel com novos componentes, layout e fotos --- frontend/src/__apps/painel/main.js | 62 +++++-- frontend/src/__apps/painel/scss/painel.scss | 59 +++---- frontend/src/components/painel/Cronometro.vue | 47 +++--- .../src/components/painel/CronometroList.vue | 89 ++++++++-- .../src/components/painel/PainelHeader.vue | 61 ++----- .../src/components/painel/PainelMateria.vue | 36 ++-- .../src/components/painel/PainelOradores.vue | 23 ++- .../components/painel/PainelParlamentares.vue | 158 ++++++++++++++++-- .../src/components/painel/PainelResultado.vue | 123 +++++++++----- sapl/templates/painel/painel_v2.html | 73 +++----- 10 files changed, 457 insertions(+), 274 deletions(-) diff --git a/frontend/src/__apps/painel/main.js b/frontend/src/__apps/painel/main.js index b09491ae0..237bd3004 100644 --- a/frontend/src/__apps/painel/main.js +++ b/frontend/src/__apps/painel/main.js @@ -13,6 +13,7 @@ import PainelParlamentares from '../../components/painel/PainelParlamentares.vue import PainelOradores from '../../components/painel/PainelOradores.vue' import PainelMateria from '../../components/painel/PainelMateria.vue' import PainelResultado from '../../components/painel/PainelResultado.vue' +import PainelFooter from '../../components/painel/PainelFooter.vue' import alarm from '../../assets/audio/ring.mp3' // register components @@ -23,6 +24,7 @@ Vue.component('painel-parlamentares', PainelParlamentares) Vue.component('painel-oradores', PainelOradores) Vue.component('painel-materia', PainelMateria) Vue.component('painel-resultado', PainelResultado) +Vue.component('painel-footer', PainelFooter) // global store Vue.use(Vuex) @@ -50,10 +52,22 @@ const store = new Vuex.Store({ }, updateParlamentares(state, votos_parlamentares) { if (votos_parlamentares) { - state.parlamentares.forEach((p)=>{ - if (p.parlamentar_id in votos_parlamentares) { - p.voto = votos_parlamentares[p.parlamentar_id].voto + let votosMap = votos_parlamentares; + if (Array.isArray(votos_parlamentares)) { + votosMap = {}; + votos_parlamentares.forEach((item) => { + if (item) { + Object.keys(item).forEach((key) => { + votosMap[key] = item[key]; + }); + } + }); + } + state.parlamentares = state.parlamentares.map((p) => { + if (p.parlamentar_id in votosMap) { + return { ...p, voto: votosMap[p.parlamentar_id].voto }; } + return p; }); } }, @@ -71,6 +85,9 @@ const store = new Vuex.Store({ }, setMostrarVoto(state, mostrar_voto) { state.mostrar_voto = mostrar_voto; + }, + setSessao(state, sessao) { + state.sessao = sessao; } }, actions: {}, @@ -105,7 +122,7 @@ new Vue({ }, computed: { - ...mapState(["painel_aberto", "sessao_aberta"]), + ...mapState(["painel_aberto", "sessao_aberta", "sessao"]), canRender () { return this.sessao_aberta && this.painel_aberto; }, @@ -113,7 +130,7 @@ new Vue({ methods: { ...mapMutations(['sessaoStatus', 'painelStatus','setParlamentares', 'updateParlamentares', 'setOradores', 'setMateria', - 'setResultado', 'setMessage', 'setMostrarVoto']), + 'setResultado', 'setMessage', 'setMostrarVoto', 'setSessao']), wsURL() { const proto = location.protocol === 'https:' ? 'wss' : 'ws' return `${proto}://${location.host}/ws/painel/${this.controllerId}/` @@ -165,6 +182,10 @@ new Vue({ this.painelStatus(data.painel_aberto); this.setMostrarVoto(data.mostrar_voto); + if (data.sessao) { + this.setSessao(data.sessao); + } + // PARLAMENTARES if (data.parlamentares) { // pre-popula para Vuex capturar mudanca de estado de 'voto' @@ -173,14 +194,14 @@ new Vue({ } // HEADER DO PAINEL - // SESSAO_PLENARIA - //TODO: group in a single SessaoPlenaria object const headerInstance = this.$refs.painelHeader; - //TODO: setup as child's props? headerInstance.sessao_plenaria = data.sessao.sessao_plenaria - headerInstance.sessao_plenaria_data = data.sessao.sessao_plenaria_data - headerInstance.sessao_plenaria_hora_inicio = data.sessao.sessao_plenaria_hora_inicio - headerInstance.brasao = data.sessao.brasao + + // FOOTER DO PAINEL (brasão + relógio) + const footerInstance = this.$refs.painelFooter; + if (footerInstance) { + footerInstance.brasao = data.sessao.brasao; + } if (data.message) { this.setMessage(data.message); @@ -194,15 +215,20 @@ new Vue({ // MATERIA if (data.materia) { this.setMateria(data.materia); - } - // RESULTADO - if (data.materia.resultado) { - this.setResultado(data.materia.resultado); - } + // RESULTADO + if (data.materia.resultado) { + this.setResultado(data.materia.resultado); - if (data.materia.resultado.votos_parlamentares) { - this.updateParlamentares(data.materia.resultado.votos_parlamentares); + if (data.materia.resultado.votos_parlamentares) { + this.updateParlamentares(data.materia.resultado.votos_parlamentares); + } + } else { + this.setResultado({}); + } + } else { + this.setMateria({}); + this.setResultado({}); } } catch (e) { console.error('Error', e); diff --git a/frontend/src/__apps/painel/scss/painel.scss b/frontend/src/__apps/painel/scss/painel.scss index aa3c060a1..d1ef31aef 100644 --- a/frontend/src/__apps/painel/scss/painel.scss +++ b/frontend/src/__apps/painel/scss/painel.scss @@ -1,43 +1,24 @@ +:root { + --bg-dark: #121212; + --bg-panel: #1e1e1e; + --text-main: #e0e0e0; + --text-highlight: #4fa64d; +} -.painel-principal { - background: #1c1b1b; - font-family: Verdana; - font-size: x-large; - .text-title { - color: #4fa64d; - margin: 0.5rem; - font-weight: bold; - } - .text-subtitle { - color: #459170; - font-weight: bold; - } - .data-hora { - font-size: 180%; - } +body { + background-color: var(--bg-dark); + color: var(--text-main); + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + margin-bottom: 0; +} - .text-value { - color: white; - } +.separador-vertical { + border-right: 1px solid #333; + min-height: 100vh; + margin: 0; +} - .logo-painel { - max-width: 100%; - } - .painels { - flex-wrap: wrap; - } - .painel{ - margin-top: 1rem; - table { - width: 100%; - } - h2 { - margin-bottom: 0.5rem; - } - #votacao, #oradores_list { - text-align: left; - display: inline-block; - margin-bottom: 1rem; - } - } +.text-subtitle { + color: #81c784; + font-weight: 600; } \ No newline at end of file diff --git a/frontend/src/components/painel/Cronometro.vue b/frontend/src/components/painel/Cronometro.vue index a77e1d777..081b3404a 100644 --- a/frontend/src/components/painel/Cronometro.vue +++ b/frontend/src/components/painel/Cronometro.vue @@ -1,18 +1,19 @@ + + + diff --git a/frontend/src/components/painel/PainelHeader.vue b/frontend/src/components/painel/PainelHeader.vue index 38b13bd6d..c4a3ff49e 100644 --- a/frontend/src/components/painel/PainelHeader.vue +++ b/frontend/src/components/painel/PainelHeader.vue @@ -1,29 +1,7 @@ -