From 728bd4fd6547df3b7f1a4c583f1adeb7cba6b45b Mon Sep 17 00:00:00 2001 From: Edward Oliveira Date: Mon, 8 Dec 2025 17:18:14 -0300 Subject: [PATCH] Add more apps --- frontend/src/__apps/painel-controle/main.js | 38 +++ .../__apps/painel-controle/scss/painel.scss | 43 ++++ frontend/src/__apps/painel/main.js | 50 ++-- frontend/src/__apps/votacao/main.js | 237 ++++++++++++++++++ frontend/src/__apps/votacao/scss/votacao.scss | 0 .../components/{ => painel}/Cronometro.vue | 0 .../{ => painel}/CronometroList.vue | 0 .../components/{ => painel}/PainelHeader.vue | 3 +- .../components/{ => painel}/PainelMateria.vue | 0 .../{ => painel}/PainelOradores.vue | 0 .../{ => painel}/PainelParlamentares.vue | 2 +- .../{ => painel}/PainelResultado.vue | 0 .../src/components/votacao/VotacaoNominal.vue | 11 + sapl/painel/consumers.py | 112 +++++---- sapl/painel/urls.py | 4 +- sapl/painel/views.py | 6 +- .../migrations/0070_views_sessao_plenaria.py | 14 +- sapl/sessao/urls.py | 10 + sapl/templates/painel/painel_v2.html | 1 + sapl/templates/sessao/painel_v2.html | 106 ++++++++ sapl/templates/sessao/votacao/votacao_v2.html | 78 ++++++ vue.config.js | 10 + 22 files changed, 633 insertions(+), 92 deletions(-) create mode 100644 frontend/src/__apps/painel-controle/main.js create mode 100644 frontend/src/__apps/painel-controle/scss/painel.scss create mode 100644 frontend/src/__apps/votacao/main.js create mode 100644 frontend/src/__apps/votacao/scss/votacao.scss rename frontend/src/components/{ => painel}/Cronometro.vue (100%) rename frontend/src/components/{ => painel}/CronometroList.vue (100%) rename frontend/src/components/{ => painel}/PainelHeader.vue (95%) rename frontend/src/components/{ => painel}/PainelMateria.vue (100%) rename frontend/src/components/{ => painel}/PainelOradores.vue (100%) rename frontend/src/components/{ => painel}/PainelParlamentares.vue (95%) rename frontend/src/components/{ => painel}/PainelResultado.vue (100%) create mode 100644 frontend/src/components/votacao/VotacaoNominal.vue create mode 100644 sapl/templates/sessao/painel_v2.html create mode 100644 sapl/templates/sessao/votacao/votacao_v2.html diff --git a/frontend/src/__apps/painel-controle/main.js b/frontend/src/__apps/painel-controle/main.js new file mode 100644 index 000000000..8e405475c --- /dev/null +++ b/frontend/src/__apps/painel-controle/main.js @@ -0,0 +1,38 @@ +import './scss/painel.scss' +import Vue from 'vue' +import { FormSelectPlugin } from 'bootstrap-vue' +import axios from 'axios' + +//TODO: incluir painel-controle dentro da app de painel, colocando rotas diferentes + +axios.defaults.xsrfCookieName = 'csrftoken' +axios.defaults.xsrfHeaderName = 'X-CSRFToken' + +Vue.use(FormSelectPlugin) + +console.log('painel controle main.js carregado') + +const v = new Vue({ // eslint-disable-line + delimiters: ['[[', ']]'], + el: '#painel-controle', + data () { + return { + sessao_plenaria: "74ª Sessão Ordinária da 1ª Sessão Legislativa da 18ª Legislatura", + message: "", + } + }, + + watch: {}, + + computed: { + + }, + + created () {}, + + methods: {}, + + mounted () { + console.log("Painel controle app mounted!") + } +}) diff --git a/frontend/src/__apps/painel-controle/scss/painel.scss b/frontend/src/__apps/painel-controle/scss/painel.scss new file mode 100644 index 000000000..aa3c060a1 --- /dev/null +++ b/frontend/src/__apps/painel-controle/scss/painel.scss @@ -0,0 +1,43 @@ + +.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%; + } + + .text-value { + color: white; + } + + .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; + } + } +} \ No newline at end of file diff --git a/frontend/src/__apps/painel/main.js b/frontend/src/__apps/painel/main.js index def3ca411..c78642a91 100644 --- a/frontend/src/__apps/painel/main.js +++ b/frontend/src/__apps/painel/main.js @@ -6,13 +6,13 @@ import Vuex from 'vuex' import { mapState } from 'vuex'; import { mapMutations } from 'vuex' -import Cronometro from '../../components/Cronometro.vue' -import CronometroList from '../../components/CronometroList.vue' -import PainelHeader from '../../components/PainelHeader.vue' -import PainelParlamentares from '../../components/PainelParlamentares.vue' -import PainelOradores from '../../components/PainelOradores.vue' -import PainelMateria from '../../components/PainelMateria.vue' -import PainelResultado from '../../components/PainelResultado.vue' +import Cronometro from '../../components/painel/Cronometro.vue' +import CronometroList from '../../components/painel/CronometroList.vue' +import PainelHeader from '../../components/painel/PainelHeader.vue' +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 alarm from '../../assets/audio/ring.mp3' // register components @@ -49,11 +49,13 @@ const store = new Vuex.Store({ state.parlamentares = parlamentares; }, updateParlamentares(state, votos_parlamentares) { - state.parlamentares.forEach((p)=>{ - if (p.parlamentar_id in votos_parlamentares) { - p.voto = votos_parlamentares[p.parlamentar_id].voto - } - }); + if (votos_parlamentares) { + state.parlamentares.forEach((p)=>{ + if (p.parlamentar_id in votos_parlamentares) { + p.voto = votos_parlamentares[p.parlamentar_id].voto + } + }); + } }, setOradores(state, oradores) { state.oradores = oradores; @@ -85,17 +87,6 @@ new Vue({ data() { return { controllerId: null, - // TODO: state here is really needed? - state: { - sessao_plenaria: '', - sessao_plenaria_data: '', - sessao_plenaria_hora_inicio: '', - votacao: [], - parlamentares: [], - oradores: [], - materia: {}, - resultado: {}, - }, ws: null, isOpen: false, error: null, @@ -104,6 +95,7 @@ new Vue({ } }, mounted() { + console.log('Painel principal mounted!') // $el is guaranteed here const el = this.$el // prefer data-attr; fallback to global if you set it @@ -206,12 +198,12 @@ new Vue({ } // RESULTADO - if (data.resultado) { - this.setResultado(data.resultado); + if (data.materia.resultado) { + this.setResultado(data.materia.resultado); } - if (data.votos_parlamentares) { - this.updateParlamentares(data.votos_parlamentares); + if (data.materia.resultado.votos_parlamentares) { + this.updateParlamentares(data.materia.resultado.votos_parlamentares); } } catch (e) { console.error('Error', e); @@ -225,12 +217,12 @@ new Vue({ this.isOpen = true; this.error = null - console.log(`✅ WebSocket connected: ${url}`); + console.log(`✅ WebSocket connected to ${url}`); // send an initial message to the server this.ws.send(JSON.stringify({ type: "echo", message: "Client connected" })); - // Ping keep-alive + // ping keep-alive timer this.pingTimer = setInterval(() => { const ping = JSON.stringify({ type: "ping", ts: Date.now()}); console.log(`Sending ping ${ping}`) diff --git a/frontend/src/__apps/votacao/main.js b/frontend/src/__apps/votacao/main.js new file mode 100644 index 000000000..07069d600 --- /dev/null +++ b/frontend/src/__apps/votacao/main.js @@ -0,0 +1,237 @@ +import './scss/votacao.scss' +import Vue from 'vue' +import { FormSelectPlugin } from 'bootstrap-vue' +import axios from 'axios' + +axios.defaults.xsrfCookieName = 'csrftoken' +axios.defaults.xsrfHeaderName = 'X-CSRFToken' + +Vue.use(FormSelectPlugin) + +console.log('votacao main.js carregado') + +const v = new Vue({ // eslint-disable-line + delimiters: ['[[', ']]'], + el: '#votacao', + data () { + return { + votacao_aberta: true, + edit_votes: true, + disable_resultado: false, + resultado_selected: "", + observacoes: "", + error_message: "", + tipo_votacao: "Votação Nominal", + materia: "Projeto de Lei Ordinária nº 3 de 2025", + ementa: "Institui no Município de Pato Branco o Projeto Chá de Fralda Social. ", + parlamentares: [ + { + "parlamentar_id": 197, + "nome_parlamentar": "Alexandre Zoche", + "filiacao": "PRD" + }, + { + "parlamentar_id": 196, + "nome_parlamentar": "Anne Cristine Gomes da Silva Cavali", + "filiacao": "PSD" + }, + { + "parlamentar_id": 194, + "nome_parlamentar": "Diogo Domingos Grando", + "filiacao": "PRD" + }, + { + "parlamentar_id": 186, + "nome_parlamentar": "Eduardo Albani Dala Costa", + "filiacao": "Republicanos" + }, + { + "parlamentar_id": 3, + "nome_parlamentar": "Fabricio Preis de Mello", + "filiacao": "PL" + }, + { + "parlamentar_id": 6, + "nome_parlamentar": "Joecir Bernardi", + "filiacao": "PSD" + }, + { + "parlamentar_id": 187, + "nome_parlamentar": "Lindomar Rodrigo Brandão", + "filiacao": "PP" + }, + { + "parlamentar_id": 195, + "nome_parlamentar": "Rafael Foss", + "filiacao": "União" + }, + { + "parlamentar_id": 11, + "nome_parlamentar": "Rodrigo José Correia", + "filiacao": "União" + }, + { + "parlamentar_id": 192, + "nome_parlamentar": "Thania Maria Caminski Gehlen", + "filiacao": "PP" + } + ], + //TODO: check if votos_parlamentares is null + votos_parlamentares: { + 186: { + "voto": "Não", + "materia_id": 31919, + "parlamentar_id": 186, + "parlamentar_nome": "Eduardo Albani Dala Costa" + }, + 195: { + "voto": "Não", + "materia_id": 31919, + "parlamentar_id": 195, + "parlamentar_nome": "Rafael Foss" + }, + 196: { + "voto": "Não", + "materia_id": 31919, + "parlamentar_id": 196, + "parlamentar_nome": "Anne Cristine Gomes da Silva Cavali" + }, + 3: { + "voto": "Sim", + "materia_id": 31919, + "parlamentar_id": 3, + "parlamentar_nome": "Fabricio Preis de Mello" + }, + 11: { + "voto": "Não", + "materia_id": 31919, + "parlamentar_id": 11, + "parlamentar_nome": "Rodrigo José Correia" + }, + 194: { + "voto": "Não", + "materia_id": 31919, + "parlamentar_id": 194, + "parlamentar_nome": "Diogo Domingos Grando" + }, + 197: { + "voto": "Não", + "materia_id": 31919, + "parlamentar_id": 197, + "parlamentar_nome": "Alexandre Zoche" + }, + 6: { + "voto": "Não", + "materia_id": 31919, + "parlamentar_id": 6, + "parlamentar_nome": "Joecir Bernardi" + }, + 187: { + "voto": "Abstenção", + "materia_id": 31919, + "parlamentar_id": 187, + "parlamentar_nome": "Lindomar Rodrigo Brandão" + }, + 192: { + "voto": "Sim", + "materia_id": 31919, + "parlamentar_id": 192, + "parlamentar_nome": "Thania Maria Caminski Gehlen" + } + }, + options: [ + { text: 'Sim', value: 'voto_sim' }, + { text: 'Não', value: 'voto_nao' }, + { text: 'Abstenção', value: 'abstencao' }, + { text: 'Não Votou', value: 'nao_votou' }, + ], + tipos_resultados: [ + { + "id": 13, + "nome": "Aprovada a retirada de pauta" + }, + { + "id": 10, + "nome": "Aprovada por dois terços" + }, + { + "id": 2, + "nome": "Aprovada por maioria absoluta" + }, + { + "id": 1, + "nome": "Aprovada por maioria simples - conforme o art. 37 do RI o presidente não vota" + }, + { + "id": 8, + "nome": "Aprovada por maioria simples - conforme o art. 37 do RI o presidente votou pelo desempate" + }, + { + "id": 15, + "nome": "Aprovada." + }, + { + "id": 7, + "nome": "Empate - conforme o art. 37 do RI o presidente vota para desempate" + }, + { + "id": 16, + "nome": "IMPROCEDENTE" + }, + { + "id": 12, + "nome": "Leitura em Plenário" + }, + { + "id": 17, + "nome": "PROCEDENTE" + }, + { + "id": 11, + "nome": "Prejudicada" + }, + { + "id": 5, + "nome": "Rejeitada" + }, + { + "id": 14, + "nome": "Rejeitada a retirada de pauta" + }, + { + "id": 9, + "nome": "Rejeitada por maioria simples - conforme o art. 37 do RI o presidente votou pelo desempate" + } + ], + } + }, + + watch: {}, + + computed: { + total_votos() { + // TODO: use number index instead of string ("sim", "não") as keys. + var groupedVotes = Map.groupBy(Object.values(this.votos_parlamentares), ({ voto }) => voto ) + // initialize total_votos + total_votos = [ + {"tipo": "Sim", "total": 0}, + {"tipo": "Não", "total": 0}, + {"tipo": "Abstenção", "total": 0}, + {"tipo": "Não Votou", total: 0} + ] + for (const [key, value] of groupedVotes.entries()) { + const index = total_votos.findIndex(item => item.tipo === key); + total_votos[index].total = value.length + } + return total_votos + } + }, + + created () {}, + + methods: {}, + + mounted () { + console.log("Votacao app mounted!") + } +}) diff --git a/frontend/src/__apps/votacao/scss/votacao.scss b/frontend/src/__apps/votacao/scss/votacao.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/components/Cronometro.vue b/frontend/src/components/painel/Cronometro.vue similarity index 100% rename from frontend/src/components/Cronometro.vue rename to frontend/src/components/painel/Cronometro.vue diff --git a/frontend/src/components/CronometroList.vue b/frontend/src/components/painel/CronometroList.vue similarity index 100% rename from frontend/src/components/CronometroList.vue rename to frontend/src/components/painel/CronometroList.vue diff --git a/frontend/src/components/PainelHeader.vue b/frontend/src/components/painel/PainelHeader.vue similarity index 95% rename from frontend/src/components/PainelHeader.vue rename to frontend/src/components/painel/PainelHeader.vue index c7376d4c8..38b13bd6d 100644 --- a/frontend/src/components/PainelHeader.vue +++ b/frontend/src/components/painel/PainelHeader.vue @@ -16,7 +16,7 @@ -
+

{{ message }}

@@ -40,7 +40,6 @@ export default { sessao_plenaria_data: "22/10/2025", sessao_plenaria_hora_inicio: "13:30", brasao: "", - message: "", data_atual: "", relogio: "", currentDateTimeId: null, // stores the id returned by setInterval() diff --git a/frontend/src/components/PainelMateria.vue b/frontend/src/components/painel/PainelMateria.vue similarity index 100% rename from frontend/src/components/PainelMateria.vue rename to frontend/src/components/painel/PainelMateria.vue diff --git a/frontend/src/components/PainelOradores.vue b/frontend/src/components/painel/PainelOradores.vue similarity index 100% rename from frontend/src/components/PainelOradores.vue rename to frontend/src/components/painel/PainelOradores.vue diff --git a/frontend/src/components/PainelParlamentares.vue b/frontend/src/components/painel/PainelParlamentares.vue similarity index 95% rename from frontend/src/components/PainelParlamentares.vue rename to frontend/src/components/painel/PainelParlamentares.vue index 9754dc8a9..6bbe9809c 100644 --- a/frontend/src/components/PainelParlamentares.vue +++ b/frontend/src/components/painel/PainelParlamentares.vue @@ -7,7 +7,7 @@ {{ p.nome_parlamentar }} {{ p.filiacao }} - {{ p.voto }} + {{ p.voto }}
diff --git a/frontend/src/components/PainelResultado.vue b/frontend/src/components/painel/PainelResultado.vue similarity index 100% rename from frontend/src/components/PainelResultado.vue rename to frontend/src/components/painel/PainelResultado.vue diff --git a/frontend/src/components/votacao/VotacaoNominal.vue b/frontend/src/components/votacao/VotacaoNominal.vue new file mode 100644 index 000000000..dd569d78f --- /dev/null +++ b/frontend/src/components/votacao/VotacaoNominal.vue @@ -0,0 +1,11 @@ + + + diff --git a/sapl/painel/consumers.py b/sapl/painel/consumers.py index a52ec5666..af9b64a44 100644 --- a/sapl/painel/consumers.py +++ b/sapl/painel/consumers.py @@ -1,58 +1,79 @@ -import html +import json import json import logging import time from channels.db import database_sync_to_async from channels.generic.websocket import AsyncWebsocketConsumer, AsyncJsonWebsocketConsumer -from django.core.exceptions import ObjectDoesNotExist -from django.db.models import Q from sapl.base.models import CasaLegislativa, AppConfig -from sapl.sessao.models import SessaoPlenaria, OrdemDia, ExpedienteMateria, RegistroVotacao, RegistroLeitura, \ - PresencaOrdemDia, SessaoPlenariaPresenca, OradorExpediente, VotoParlamentar, AbstractOrdemDia, SessaoPresencaView, \ +from sapl.sessao.models import SessaoPlenaria, SessaoPresencaView, \ SessaoOradorView, SessaoMateriaVotacaoView -logger = logging.getLogger(__name__) +def get_materia_votacao(votacao): + return { + # TOO UGLY! FIX THIS if-else + "materia_id": votacao.materia.id if votacao and votacao.materia else "", + "texto": str(votacao.materia) if votacao and votacao.materia else "", + "ementa": votacao.materia.ementa if votacao and votacao.materia and votacao.materia.ementa else "", + "resultado": { + "resultado_votacao": votacao.resultado_votacao if votacao and votacao.resultado_votacao else "", + "resultado": votacao.resultado if votacao and votacao.resultado else "", + "numero_votos": votacao.numero_votos if votacao and votacao.numero_votos else {}, + "votos_parlamentares": votacao.votos_parlamentares if votacao and votacao.votos_parlamentares else [], + }, + } -def get_dados_painel(pk: int) -> dict: + +def get_dados_painel(sessao_plenaria_id: int) -> dict: app_config = AppConfig.objects.first() - sessao = SessaoPlenaria.objects.get(id=pk) + sessao = SessaoPlenaria.objects.get(id=sessao_plenaria_id) casa = CasaLegislativa.objects.first() brasao = casa.logotipo.url \ if app_config.mostrar_brasao_painel else None - # FILIACAO - # { 1: { "id": 1, "nome": "fulano", "filiacao": "aquela"}, ...} - # [ { "id": 1, "nome": "fulano", "filiacao": "aquela"}, ... ] - - # sessao_plenaria_id/pk = 2546 - # Painel - presentes = SessaoPresencaView.objects.filter(sessao_plenaria_id=pk, - etapa_sessao='expediente').values_list('parlamentar_id', - 'nome_parlamentar', - 'filiacao', ) + # TODO: recuperar outra matéria quando não existir nenhuma materia_votacao aberta! + materia_votacao = SessaoMateriaVotacaoView.objects. \ + filter(sessao_plenaria_id=sessao_plenaria_id, votacao_aberta=True).first() + + if not materia_votacao: + return { + "type": "data", + "sessao_aberta": sessao.iniciada and not sessao.finalizada, + "painel_aberto": sessao.painel_aberto, + "mostrar_voto": app_config.mostrar_voto, + "message": "PAINEL ENCONTRA-SE FECHADO" if not sessao.painel_aberto else "", + "sessao": { + "sessao_plenaria_id": sessao.id, + "brasao": brasao, + "sessao_plenaria": str(sessao), + "sessao_plenaria_data": sessao.data_inicio.strftime("%d/%m/%Y"), + "sessao_plenaria_hora_inicio": sessao.hora_inicio, + "sessao_solene": sessao.tipo.nome == "Solene", + "sessao_finalizada": sessao.finalizada, + "tema_solene": sessao.tema_solene, + }, + "message": "Nenhuma matéria aberta para votação!", + } + + presentes = SessaoPresencaView.objects.filter(sessao_plenaria_id=sessao_plenaria_id, + etapa_sessao=materia_votacao.etapa_sessao).values_list( + 'parlamentar_id', + 'nome_parlamentar', + 'filiacao', ) parlamentares = [dict(zip(['parlamentar_id', 'nome_parlamentar', 'filiacao'], p)) for p in presentes] - - oradores = SessaoOradorView.objects.filter(sessao_plenaria_id=pk, - etapa_sessao='expediente').values_list('ordem_pronunciamento', - 'nome_parlamentar', - ) + if materia_votacao and materia_votacao.numero_votos: + materia_votacao.numero_votos.update({"num_presentes": len(parlamentares)}) + + oradores = SessaoOradorView.objects.filter(sessao_plenaria_id=sessao_plenaria_id, + etapa_sessao=materia_votacao.etapa_sessao).values_list( + 'ordem_pronunciamento', + 'nome_parlamentar', + ) oradores = [dict(zip(['ordem_pronunciamento', 'nome_parlamentar'], o)) for o in oradores] - votacao = SessaoMateriaVotacaoView.objects.get(sessao_plenaria_id=pk, - etapa_sessao='expediente', - materia_id=31910) - - votos_parlamentares = {} - if votacao: - if votacao.votos_parlamentares: - votos_parlamentares = {p["parlamentar_id"]: p for p in votacao.votos_parlamentares} - if votacao.numero_votos: - votacao.numero_votos.update({"num_presentes": len(parlamentares)}) - # TODO: recover stopwatch state from DB/Cache stopwatch = { "type": "stopwatch.state", @@ -80,18 +101,8 @@ def get_dados_painel(pk: int) -> dict: }, "parlamentares": parlamentares, "oradores": oradores, - "resultado": { - "resultado_votacao": votacao.resultado_votacao, - "resultado": votacao.resultado, - "numero_votos": votacao.numero_votos, - }, - "votos_parlamentares": votos_parlamentares, - "materia": { - "materia_id": votacao.materia.id, - "texto": str(votacao.materia), - "ementa": votacao.materia.ementa, - }, - "stopwatch": stopwatch, # TODO: array of stopwatches + "materia": get_materia_votacao(materia_votacao), + "stopwatch": [stopwatch], # TODO: array of stopwatches } print(json.dumps(dados_sessao, indent=4)) @@ -99,18 +110,20 @@ def get_dados_painel(pk: int) -> dict: class PainelConsumer(AsyncJsonWebsocketConsumer): + logger = logging.getLogger(__name__) # def __init__(self): # self.group = set() # self.controller_id = None async def connect(self): + # TODO: transformar prints em log messages user = self.scope.get("user") controller_id = self.scope["url_route"]["kwargs"]["controller_id"] print(f"user: {user}, controller_id: {controller_id}") if not (user and user.is_authenticated): - log.info(f"{user} is not authenticated user!") + self.logger.info(f"{user} is not authenticated user!") await self.close(code=4401) # explicit, graceful close return @@ -144,7 +157,7 @@ class PainelConsumer(AsyncJsonWebsocketConsumer): data = json.loads(text_data or "{}") print("Received from client:", data) # TODO: turn into log messages msg_type = data.get("type") - if msg_type == "ping": + if msg_type == "ping": print("PING") await self.send_json({"type": "ping", "ts": time.time()}) return @@ -173,13 +186,14 @@ class HealthConsumer(AsyncWebsocketConsumer): WebSockets consumer that doesn"t require authentication to debug with wscat (wscat -c ws://127.0.0.1:8000/ws/painel/) """ + logger = logging.getLogger(__name__) async def connect(self): try: await self.accept() await self.send(json.dumps({"ok": True})) except Exception as e: - log.exception("connect failed: %s", e) + self.logger.exception("connect failed: %s", e) # Let Channels close with 1011 if we got here async def receive(self, text_data=None, bytes_data=None): diff --git a/sapl/painel/urls.py b/sapl/painel/urls.py index b962fb07b..915e811f6 100644 --- a/sapl/painel/urls.py +++ b/sapl/painel/urls.py @@ -27,7 +27,7 @@ urlpatterns = [ url(r'^voto-individual/$', votante_view, name='voto_individual'), - path("painel/v2", websocket_view, name='painel_websocket'), - path("painel/v2/controller//stopwatch", + path("v2/painel/", websocket_view, name='painel_websocket'), + path("v2/painel/controller//stopwatch", stopwatch_controller, name='painel_controller'), ] diff --git a/sapl/painel/views.py b/sapl/painel/views.py index bff5b3ce5..0fb80b51e 100644 --- a/sapl/painel/views.py +++ b/sapl/painel/views.py @@ -634,15 +634,13 @@ def get_dados_painel(request, pk): return response_nenhuma_materia(get_presentes(pk, response, None)) -def websocket_view(request): +def websocket_view(request, controller_id): now = timezone.localtime(timezone.now()) utc_offset = now.utcoffset().total_seconds() / 60 - # controller_id == session_id context = {'head_title': str(_('Painel Plenário')), - 'sessao_id': 2546, # TODO: recover from template call 'utc_offset': utc_offset, 'enable_live_ws': True, - 'controller_id': 2546, # TODO: unify with sessao_id + 'controller_id': controller_id, # aka, sessao_plenaria_id } return render(request, "painel/painel_v2.html", context) diff --git a/sapl/sessao/migrations/0070_views_sessao_plenaria.py b/sapl/sessao/migrations/0070_views_sessao_plenaria.py index eda5cbcb6..a1092447d 100644 --- a/sapl/sessao/migrations/0070_views_sessao_plenaria.py +++ b/sapl/sessao/migrations/0070_views_sessao_plenaria.py @@ -156,7 +156,7 @@ class Migration(migrations.Migration): ml.ementa materia_ementa, tipo_votacao, CASE tipo_votacao - WHEN 1 THEN 'Simbólia' + WHEN 1 THEN 'Simbólica' WHEN 2 THEN 'Nominal' WHEN 3 THEN 'Secreta' WHEN 4 THEN 'Leitura' @@ -184,13 +184,15 @@ class Migration(migrations.Migration): WHERE rv.expediente_id = em.id AND tipo_votacao != 4) rv ON TRUE LEFT JOIN LATERAL ( SELECT em.sessao_plenaria_id, - em.numero_ordem, + em.numero_ordem, jsonb_agg(jsonb_build_object( + 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 em.materia_id) as votos_parlamentares + )) ORDER BY p.nome_parlamentar) 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 @@ -238,13 +240,15 @@ class Migration(migrations.Migration): WHERE rv.expediente_id = od.id AND tipo_votacao != 4) rv ON TRUE LEFT JOIN LATERAL ( SELECT od.sessao_plenaria_id, - od.numero_ordem, + od.numero_ordem, jsonb_agg(jsonb_build_object( + 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 od.materia_id) as votos_parlamentares + )) ORDER BY p.nome_parlamentar) 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 diff --git a/sapl/sessao/urls.py b/sapl/sessao/urls.py index 05e19a844..c0c7c2bea 100644 --- a/sapl/sessao/urls.py +++ b/sapl/sessao/urls.py @@ -42,6 +42,8 @@ from sapl.sessao.views import (AdicionarVariasMateriasExpediente, CorrespondenciaCrud, recuperar_documento) +from django.views.generic import TemplateView + from .apps import AppConfig app_name = AppConfig.name @@ -171,6 +173,14 @@ urlpatterns = [ ResumoAtaView.as_view(), name='resumo_ata'), url(r'^sessao/pesquisar-sessao$', PesquisarSessaoPlenariaView.as_view(), name='pesquisar_sessao'), + + # TODO: create proper view + url(r'^sessao/(?P\d+)/v2/votacao$', + TemplateView.as_view(template_name='sessao/votacao/votacao_v2.html'), + name='votacaonominal'), + url(r'^sessao/(?P\d+)/v2/painel$', + TemplateView.as_view(template_name='sessao/painel_v2.html')), + url(r'^sessao/(?P\d+)/matordemdia/votnom/(?P\d+)/(?P\d+)$', VotacaoNominalView.as_view(), name='votacaonominal'), url(r'^sessao/(?P\d+)/matordemdia/votnom/edit/(?P\d+)/(?P\d+)$', diff --git a/sapl/templates/painel/painel_v2.html b/sapl/templates/painel/painel_v2.html index e8ceeb76e..fc38b4199 100644 --- a/sapl/templates/painel/painel_v2.html +++ b/sapl/templates/painel/painel_v2.html @@ -45,6 +45,7 @@
+ [[ message ]]
diff --git a/sapl/templates/sessao/painel_v2.html b/sapl/templates/sessao/painel_v2.html new file mode 100644 index 000000000..e5371b50a --- /dev/null +++ b/sapl/templates/sessao/painel_v2.html @@ -0,0 +1,106 @@ +{% extends "crud/detail.html" %} +{% load i18n %} +{% load staticfiles %} +{% load common_tags %} +{% load render_bundle from webpack_loader %} +{% load webpack_static from webpack_loader %} + +{% block actions %} {% endblock %} + +{% block title %} +{% endblock %} + +{% block vue_content %} +
+

+ Painel Eletrônico ([[ sessao_plenaria ]]) +

+ [[ message ]] +
+ +
+
+
+
+ +

Operação do Painel Eletrônico

+

+
+
+

Cronômetro do Discurso

+
+ +
+
+
+
+ +
+
+
+
+ +

+
+

Cronômetro do Aparte

+
+ +
+
+
+
+ +
+
+
+
+ +

+
+

Cronômetro da Questão de Ordem

+
+ +
+
+
+
+ +
+
+
+
+ +
+
+ +
+

Cronômetro de Considerações Finais

+
+ +
+
+
+
+ +
+
+
+
+

+ +
+
+
+
+{% endblock vue_content %} + +{% block webpack_loader_css %} + {{ block.super }} + {% render_bundle 'votacao' 'css' %} +{% endblock %} + +{% block webpack_loader_js %} + {% render_chunk_vendors 'js' %} + {% render_bundle 'global' 'js' %} + {% render_bundle 'painel-controle' 'js' %} +{% endblock %} diff --git a/sapl/templates/sessao/votacao/votacao_v2.html b/sapl/templates/sessao/votacao/votacao_v2.html new file mode 100644 index 000000000..33f9d3304 --- /dev/null +++ b/sapl/templates/sessao/votacao/votacao_v2.html @@ -0,0 +1,78 @@ +{% extends "crud/detail.html" %} +{% load i18n %} +{% load crispy_forms_tags cropping%} +{% load common_tags %} +{% load render_bundle from webpack_loader %} +{% load webpack_static from webpack_loader %} + +{% block vue_content %} + +
+
+ [[ tipo_votacao ]] + [[ error_message ]] + + +
+ Matéria: [[ materia ]] +
+ Ementa: [[ ementa ]] +
+
+
+ +
+ Votos +
+
[[ p.nome_parlamentar ]]
+
[[ p.filiacao ]]
+
+ + + +   + +
+
+ [[ votos_parlamentares[p.parlamentar_id].voto ]] +
+
+
+
+ Situação da Votação: +
+
[[ voto.tipo ]]: [[ voto.total ]]
+
+
+ +
+ Resultado da Votação: + +
+ +
+
+ Observações: + +
+
+
+ +
+
+{% endblock vue_content %} + +{% block webpack_loader_css %} + {{ block.super }} + {% render_bundle 'votacao' 'css' %} +{% endblock %} + +{% block webpack_loader_js %} + {% render_chunk_vendors 'js' %} + {% render_bundle 'global' 'js' %} + {% render_bundle 'votacao' 'js' %} +{% endblock %} diff --git a/vue.config.js b/vue.config.js index d1915aefc..11504aa3f 100644 --- a/vue.config.js +++ b/vue.config.js @@ -137,11 +137,21 @@ module.exports = { .add('./frontend/src/__apps/parlamentar/main.js') .end() + config + .entry('votacao') + .add('./frontend/src/__apps/votacao/main.js') + .end() + config .entry('painel') .add('./frontend/src/__apps/painel/main.js') .end() + config + .entry('painel-controle') + .add('./frontend/src/__apps/painel-controle/main.js') + .end() + config .entry('compilacao') .add('./frontend/src/__apps/compilacao/main.js')