mirror of https://github.com/interlegis/sapl.git
committed by
GitHub
22 changed files with 1523 additions and 570 deletions
@ -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; |
|||
} |
|||
@ -1,238 +1,383 @@ |
|||
import './scss/votacao.scss' |
|||
import Vue from 'vue' |
|||
import { FormSelectPlugin } from 'bootstrap-vue' |
|||
import Vuex from 'vuex' |
|||
import { mapState, mapMutations } from 'vuex' |
|||
import axios from 'axios' |
|||
|
|||
// Import components
|
|||
import VotacaoNominal from '../../components/votacao/VotacaoNominal.vue' |
|||
import VotacaoMateria from '../../components/votacao/VotacaoMateria.vue' |
|||
import VotacaoVotos from '../../components/votacao/VotacaoVotos.vue' |
|||
import VotacaoResultado from '../../components/votacao/VotacaoResultado.vue' |
|||
import VotacaoObservacoes from '../../components/votacao/VotacaoObservacoes.vue' |
|||
|
|||
// Register components globally
|
|||
Vue.component('votacao-nominal', VotacaoNominal) |
|||
Vue.component('votacao-materia', VotacaoMateria) |
|||
Vue.component('votacao-votos', VotacaoVotos) |
|||
Vue.component('votacao-resultado', VotacaoResultado) |
|||
Vue.component('votacao-observacoes', VotacaoObservacoes) |
|||
|
|||
axios.defaults.xsrfCookieName = 'csrftoken' |
|||
axios.defaults.xsrfHeaderName = 'X-CSRFToken' |
|||
|
|||
Vue.use(FormSelectPlugin) |
|||
Vue.use(Vuex) |
|||
|
|||
console.log('votacao main.js carregado') |
|||
|
|||
// Vuex store
|
|||
const store = new Vuex.Store({ |
|||
state: { |
|||
sessao_aberta: false, |
|||
painel_aberto: false, |
|||
mostrar_voto: false, |
|||
sessao: {}, |
|||
parlamentares: [], |
|||
oradores: [], |
|||
materia: {}, |
|||
resultado: {}, |
|||
message: '', |
|||
tipos_resultado: [], |
|||
}, |
|||
mutations: { |
|||
sessaoStatus (state, value) { |
|||
state.sessao_aberta = value |
|||
}, |
|||
painelStatus (state, value) { |
|||
state.painel_aberto = value |
|||
}, |
|||
setParlamentares (state, parlamentares) { |
|||
state.parlamentares = parlamentares |
|||
}, |
|||
updateParlamentarVoto (state, { parlamentar_id, voto, voted }) { |
|||
const p = state.parlamentares.find(p => p.parlamentar_id === parlamentar_id) |
|||
if (p) { |
|||
Vue.set(p, 'voto', voto) |
|||
if (voted !== undefined) { |
|||
Vue.set(p, 'voted', voted) |
|||
} |
|||
} |
|||
}, |
|||
setOradores (state, oradores) { |
|||
state.oradores = oradores |
|||
}, |
|||
setMateria (state, materia) { |
|||
state.materia = materia |
|||
}, |
|||
setResultado (state, resultado) { |
|||
state.resultado = resultado |
|||
}, |
|||
setMessage (state, message) { |
|||
state.message = message |
|||
}, |
|||
setMostrarVoto (state, mostrar_voto) { |
|||
state.mostrar_voto = mostrar_voto |
|||
}, |
|||
setSessao (state, sessao) { |
|||
state.sessao = sessao |
|||
}, |
|||
setTiposResultado (state, tipos) { |
|||
state.tipos_resultado = tipos |
|||
} |
|||
}, |
|||
getters: { |
|||
totalVotos (state) { |
|||
const totals = [ |
|||
{ tipo: 'Sim', total: 0 }, |
|||
{ tipo: 'Não', total: 0 }, |
|||
{ tipo: 'Abstenção', total: 0 }, |
|||
{ tipo: 'Não Votou', total: 0 } |
|||
] |
|||
state.parlamentares.forEach(p => { |
|||
if (p.voto) { |
|||
const entry = totals.find(t => t.tipo === p.voto) |
|||
if (entry) entry.total++ |
|||
} |
|||
}) |
|||
return totals |
|||
}, |
|||
numPresentes (state) { |
|||
return state.parlamentares.length |
|||
}, |
|||
totalVotados (state) { |
|||
return state.parlamentares.filter(p => p.voto && p.voto !== 'Não Votou').length |
|||
} |
|||
} |
|||
}) |
|||
|
|||
const BACKOFF = 500 |
|||
const PING_INTERVAL = 30000 |
|||
|
|||
const v = new Vue({ // eslint-disable-line
|
|||
store, |
|||
delimiters: ['[[', ']]'], |
|||
el: '#votacao', |
|||
data () { |
|||
return { |
|||
votacao_aberta: true, |
|||
edit_votes: true, |
|||
disable_resultado: false, |
|||
resultado_selected: "", |
|||
observacoes: "", |
|||
error_message: "", |
|||
tipo_votacao: 2, |
|||
tipo_votacao_descricao: "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" |
|||
} |
|||
], |
|||
controllerId: null, |
|||
ws: null, |
|||
isOpen: false, |
|||
error: null, |
|||
pingTimer: null, |
|||
reconnectTimer: null, |
|||
error_message: '', |
|||
} |
|||
}, |
|||
|
|||
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 |
|||
...mapState([ |
|||
'sessao_aberta', 'painel_aberto', 'sessao', |
|||
'parlamentares', 'materia', 'resultado', 'message', 'mostrar_voto' |
|||
]), |
|||
}, |
|||
|
|||
mounted () { |
|||
console.log('Votacao app mounted!') |
|||
const el = this.$el |
|||
this.controllerId = el.dataset.controllerId || window.controllerId |
|||
console.log(`Votacao ControllerId: ${this.controllerId}`) |
|||
if (this.controllerId) { |
|||
this.connectWS() |
|||
} else { |
|||
this.error_message = 'Erro: controller_id não definido. Não é possível conectar ao WebSocket.' |
|||
console.error('No controllerId found — WebSocket will not connect.') |
|||
} |
|||
}, |
|||
|
|||
created () {}, |
|||
methods: { |
|||
...mapMutations([ |
|||
'sessaoStatus', 'painelStatus', 'setParlamentares', |
|||
'updateParlamentarVoto', 'setOradores', 'setMateria', |
|||
'setResultado', 'setMessage', 'setMostrarVoto', 'setSessao', |
|||
'setTiposResultado' |
|||
]), |
|||
|
|||
methods: {}, |
|||
wsURL () { |
|||
const proto = location.protocol === 'https:' ? 'wss' : 'ws' |
|||
return `${proto}://${location.host}/ws/painel/${this.controllerId}/` |
|||
}, |
|||
|
|||
mounted () { |
|||
console.log("Votacao app mounted!") |
|||
voteURL () { |
|||
return `/v2/painel/controller/${this.controllerId}/vote` |
|||
}, |
|||
|
|||
castVote ({ parlamentar_id, voto }) { |
|||
console.log(`Casting vote: parlamentar=${parlamentar_id}, voto=${voto}`) |
|||
axios.post(this.voteURL(), { |
|||
parlamentar_id: parlamentar_id, |
|||
voto: voto |
|||
}, { |
|||
headers: { 'Content-Type': 'application/json' } |
|||
}) |
|||
.then(response => { |
|||
console.log('Vote cast successfully:', response.data) |
|||
}) |
|||
.catch(error => { |
|||
console.error('Error casting vote:', error.response ? error.response.data : error) |
|||
this.error_message = 'Erro ao registrar voto. Tente novamente.' |
|||
}) |
|||
}, |
|||
|
|||
cancelURL () { |
|||
return `/v2/painel/controller/${this.controllerId}/cancel` |
|||
}, |
|||
|
|||
closeURL () { |
|||
return `/v2/painel/controller/${this.controllerId}/close` |
|||
}, |
|||
|
|||
onCancelar () { |
|||
console.log('Votação cancelada') |
|||
axios.post(this.cancelURL(), {}, { |
|||
headers: { 'Content-Type': 'application/json' } |
|||
}) |
|||
.then(response => { |
|||
console.log('Votação cancelada com sucesso:', response.data) |
|||
if (response.data.redirect_url) { |
|||
window.location.href = response.data.redirect_url |
|||
} |
|||
}) |
|||
.catch(error => { |
|||
console.error('Erro ao cancelar votação:', error.response ? error.response.data : error) |
|||
const msg = error.response && error.response.data && error.response.data.message |
|||
? error.response.data.message |
|||
: 'Erro ao cancelar votação. Tente novamente.' |
|||
this.error_message = msg |
|||
}) |
|||
}, |
|||
|
|||
onFechar (data) { |
|||
console.log('Fechar votação:', data) |
|||
if (!data.resultado_selected) { |
|||
this.error_message = 'Não é possível finalizar a votação sem nenhum resultado da votação.' |
|||
return |
|||
} |
|||
axios.post(this.closeURL(), { |
|||
resultado_id: data.resultado_selected, |
|||
observacoes: data.observacoes || '' |
|||
}, { |
|||
headers: { 'Content-Type': 'application/json' } |
|||
}) |
|||
.then(response => { |
|||
console.log('Votação finalizada com sucesso:', response.data) |
|||
this.error_message = '' |
|||
if (response.data.redirect_url) { |
|||
window.location.href = response.data.redirect_url |
|||
} |
|||
}) |
|||
.catch(error => { |
|||
console.error('Erro ao fechar votação:', error.response ? error.response.data : error) |
|||
const msg = error.response && error.response.data && error.response.data.message |
|||
? error.response.data.message |
|||
: 'Erro ao fechar votação. Tente novamente.' |
|||
this.error_message = msg |
|||
}) |
|||
}, |
|||
|
|||
updateState (data) { |
|||
try { |
|||
this.sessaoStatus(data.sessao_aberta) |
|||
this.painelStatus(data.painel_aberto) |
|||
this.setMostrarVoto(data.mostrar_voto) |
|||
|
|||
if (data.sessao) { |
|||
this.setSessao(data.sessao) |
|||
} |
|||
|
|||
if (data.parlamentares) { |
|||
data.parlamentares.forEach(p => { |
|||
p.voto = p.voto || '' |
|||
}) |
|||
this.setParlamentares(data.parlamentares) |
|||
} |
|||
|
|||
if (data.message) { |
|||
this.setMessage(data.message) |
|||
} |
|||
|
|||
if (data.oradores) { |
|||
this.setOradores(data.oradores) |
|||
} |
|||
|
|||
if (data.materia) { |
|||
this.setMateria(data.materia) |
|||
|
|||
if (data.materia.resultado) { |
|||
this.setResultado(data.materia.resultado) |
|||
|
|||
// If there are existing votes, apply them to parlamentares
|
|||
if (data.materia.resultado.votos_parlamentares) { |
|||
const votos = data.materia.resultado.votos_parlamentares |
|||
if (Array.isArray(votos)) { |
|||
// Array format: [{parlamentar_id: X, voto: "Sim"}, ...]
|
|||
votos.forEach(v => { |
|||
if (v && v.parlamentar_id && v.voto) { |
|||
this.updateParlamentarVoto({ |
|||
parlamentar_id: v.parlamentar_id, |
|||
voto: v.voto |
|||
}) |
|||
} |
|||
}) |
|||
} else if (typeof votos === 'object') { |
|||
// Object format from DB view: {"5": {"voto": "Sim", "parlamentar_id": 5, ...}, ...}
|
|||
Object.keys(votos).forEach(key => { |
|||
const v = votos[key] |
|||
if (v && v.voto) { |
|||
this.updateParlamentarVoto({ |
|||
parlamentar_id: parseInt(key), |
|||
voto: v.voto |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
} else { |
|||
this.setResultado({}) |
|||
} |
|||
} else { |
|||
this.setMateria({}) |
|||
this.setResultado({}) |
|||
} |
|||
|
|||
if (data.tipos_resultado) { |
|||
this.setTiposResultado(data.tipos_resultado) |
|||
} |
|||
} catch (e) { |
|||
console.error('Error updating state:', e) |
|||
} |
|||
}, |
|||
|
|||
handleVoteUpdate (data) { |
|||
console.log('Received vote update via WebSocket:', data) |
|||
this.updateParlamentarVoto({ |
|||
parlamentar_id: data.parlamentar_id, |
|||
voto: data.voto |
|||
}) |
|||
}, |
|||
|
|||
connectWS () { |
|||
const url = this.wsURL() |
|||
this.ws = new WebSocket(url) |
|||
|
|||
this.ws.addEventListener('open', () => { |
|||
this.isOpen = true |
|||
this.error = null |
|||
this.error_message = '' |
|||
console.log(`✅ Votação WebSocket connected to ${url}`) |
|||
this.ws.send(JSON.stringify({ type: 'echo', message: 'Votacao client connected' })) |
|||
|
|||
this.pingTimer = setInterval(() => { |
|||
this.ws.send(JSON.stringify({ type: 'ping', ts: Date.now() })) |
|||
}, PING_INTERVAL) |
|||
}) |
|||
|
|||
this.ws.addEventListener('message', (message) => { |
|||
try { |
|||
const data = JSON.parse(message.data) |
|||
console.debug('Votacao WS message:', data) |
|||
|
|||
if (data.type === 'data') { |
|||
this.updateState(data) |
|||
} else if (data.type === 'vote.update') { |
|||
this.handleVoteUpdate(data) |
|||
} else if (data.type === 'echo') { |
|||
console.log('Votacao echo:', data) |
|||
} else if (data.type === 'ping') { |
|||
// ignore pings
|
|||
} |
|||
} catch (e) { |
|||
console.error('Votacao WS parse error:', e) |
|||
} |
|||
}) |
|||
|
|||
this.ws.addEventListener('close', (e) => { |
|||
console.log('❌ Votação WebSocket closed:', e) |
|||
this.isOpen = false |
|||
this.reconnectTimer = setTimeout(() => this.connectWS(), BACKOFF) |
|||
}) |
|||
|
|||
this.ws.addEventListener('error', (e) => { |
|||
this.error = e |
|||
console.error('❌ Votação WebSocket error:', e) |
|||
}) |
|||
}, |
|||
|
|||
closeWS () { |
|||
try { |
|||
this.ws && this.ws.close() |
|||
} catch (_) { |
|||
console.log('Error closing WS') |
|||
} |
|||
}, |
|||
|
|||
beforeDestroy () { |
|||
if (this.pingTimer) { |
|||
clearInterval(this.pingTimer) |
|||
} |
|||
if (this.reconnectTimer) { |
|||
clearTimeout(this.reconnectTimer) |
|||
} |
|||
this.closeWS() |
|||
} |
|||
} |
|||
}) |
|||
|
|||
@ -0,0 +1,62 @@ |
|||
<template> |
|||
<div class="painel position-relative bottom-0 hora-brasao"> |
|||
<div class="row justify-content-center align-items-center"> |
|||
<div class="col-4 text-center" id="logo-container" v-if="brasao"> |
|||
<img :src="brasao" id="logo-painel" class="logo-painel" alt="Brasão" /> |
|||
</div> |
|||
<div :class="['text-center', brasao ? 'col-4' : 'col-12 mt-5']" id="relogio-container"> |
|||
<span class="fs-1 fw-bold" id="relogio">{{ relogio }}</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { mapState } from 'vuex'; |
|||
export default { |
|||
name: 'PainelFooter', |
|||
props: { |
|||
}, |
|||
|
|||
data() { |
|||
return { |
|||
brasao: "", |
|||
data_atual: "", |
|||
relogio: "", |
|||
currentDateTimeId: null, |
|||
} |
|||
}, |
|||
|
|||
methods: { |
|||
startCurrentDateTime() { |
|||
this.data_atual = moment().utcOffset(-3).format("DD/MM/YY"); |
|||
this.currentDateTimeId = setInterval(() => { |
|||
this.relogio = moment.utc().utcOffset(-3).format("HH:mm:ss"); |
|||
}, 500); |
|||
}, |
|||
}, |
|||
beforeDestroy() { |
|||
if (this.currentDateTimeId) { |
|||
clearInterval(this.currentDateTimeId); |
|||
console.log('currentDateTimeId Interval cleared.'); |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapState(["sessao_aberta", "painel_aberto"]) |
|||
}, |
|||
mounted() { |
|||
console.log('PainelFooter component mounted'); |
|||
this.startCurrentDateTime(); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.logo-painel { |
|||
max-height: 150px; |
|||
width: auto; |
|||
} |
|||
.hora-brasao { |
|||
border-top: 1px solid #333; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,25 @@ |
|||
<template> |
|||
<div v-if="materia && materia.texto"> |
|||
<div> |
|||
Matéria: {{ materia.texto }} |
|||
<br /> |
|||
Ementa: {{ materia.ementa }} |
|||
</div> |
|||
</div> |
|||
<div v-else class="alert alert-info"> |
|||
Nenhuma matéria aberta para votação. |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { mapState } from 'vuex'; |
|||
export default { |
|||
name: 'VotacaoMateria', |
|||
computed: { |
|||
...mapState(['materia']) |
|||
}, |
|||
mounted() { |
|||
console.log('VotacaoMateria mounted'); |
|||
} |
|||
}; |
|||
</script> |
|||
@ -1,11 +1,73 @@ |
|||
<template> |
|||
<div class="VotacaoNominal"> |
|||
<pre v-text="$attrs"/> |
|||
</div> |
|||
<fieldset> |
|||
<legend>Votação Nominal</legend> |
|||
|
|||
<div v-if="error_message" class="alert alert-danger"> |
|||
{{ error_message }} |
|||
</div> |
|||
|
|||
<votacao-materia></votacao-materia> |
|||
<br /> |
|||
|
|||
<votacao-votos @cast-vote="onCastVote" ref="votos"></votacao-votos> |
|||
|
|||
<votacao-resultado ref="resultado"></votacao-resultado> |
|||
|
|||
<votacao-observacoes |
|||
:observacoes.sync="observacoes" |
|||
:resultado-selected.sync="resultado_selected" |
|||
:disabled="disable_resultado" |
|||
@cancelar="onCancelar" |
|||
@fechar="onFechar" |
|||
ref="observacoesComp"> |
|||
</votacao-observacoes> |
|||
</fieldset> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'Votacaonominal' |
|||
}; |
|||
</script> |
|||
import { mapState } from 'vuex'; |
|||
export default { |
|||
name: 'VotacaoNominal', |
|||
props: { |
|||
isOpen: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
errorMessage: { |
|||
type: String, |
|||
default: '' |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
edit_votes: true, |
|||
disable_resultado: false, |
|||
resultado_selected: '', |
|||
observacoes: '', |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapState(['parlamentares', 'materia']), |
|||
error_message() { |
|||
return this.errorMessage; |
|||
} |
|||
}, |
|||
methods: { |
|||
onCastVote({ parlamentar_id, voto }) { |
|||
this.$emit('cast-vote', { parlamentar_id, voto }); |
|||
}, |
|||
onCancelar() { |
|||
this.$emit('cancelar'); |
|||
}, |
|||
onFechar() { |
|||
this.$emit('fechar', { |
|||
resultado_selected: this.resultado_selected, |
|||
observacoes: this.observacoes |
|||
}); |
|||
} |
|||
}, |
|||
mounted() { |
|||
console.log('VotacaoNominal mounted'); |
|||
} |
|||
}; |
|||
</script> |
|||
@ -0,0 +1,84 @@ |
|||
<template> |
|||
<div v-if="parlamentares && parlamentares.length > 0"> |
|||
<div class="row"> |
|||
<div class="col-md-12"> |
|||
<label for="resultado_votacao"><strong>Resultado da Votação</strong></label> |
|||
<select v-model="resultadoSelecionado" id="resultado_votacao" class="select form-control" :disabled="disabled"> |
|||
<option value="">---------</option> |
|||
<option v-for="tipo in tipos_resultado" |
|||
:key="tipo.id" |
|||
:value="tipo.id"> |
|||
{{ tipo.nome }} |
|||
</option> |
|||
</select> |
|||
</div> |
|||
</div> |
|||
|
|||
<br /> |
|||
<div class="row"> |
|||
<div class="col-md-12"> |
|||
Observações<br/> |
|||
<textarea id="observacao" v-model="textoObservacoes" style="width:100%;" rows="7"></textarea> |
|||
</div> |
|||
</div> |
|||
|
|||
<br /><br /> |
|||
<div class="row"> |
|||
<div class="col-md-12"> |
|||
<div class="form-group row justify-content-between"> |
|||
<input type="button" id="cancelar-votacao" value="Cancelar Votação" |
|||
class="btn btn-warning" @click="$emit('cancelar')" /> |
|||
<input type="button" id="salvar-votacao" value="Fechar Votação" |
|||
class="btn btn-primary" @click="$emit('fechar')" /> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { mapState } from 'vuex'; |
|||
export default { |
|||
name: 'VotacaoObservacoes', |
|||
props: { |
|||
disabled: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
observacoes: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
resultadoSelected: { |
|||
type: [String, Number], |
|||
default: '' |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
textoObservacoes: this.observacoes, |
|||
resultadoSelecionado: this.resultadoSelected, |
|||
} |
|||
}, |
|||
watch: { |
|||
observacoes(val) { |
|||
this.textoObservacoes = val; |
|||
}, |
|||
textoObservacoes(val) { |
|||
this.$emit('update:observacoes', val); |
|||
}, |
|||
resultadoSelected(val) { |
|||
this.resultadoSelecionado = val; |
|||
}, |
|||
resultadoSelecionado(val) { |
|||
this.$emit('update:resultadoSelected', val); |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapState(['parlamentares', 'tipos_resultado']) |
|||
}, |
|||
mounted() { |
|||
console.log('VotacaoObservacoes mounted'); |
|||
} |
|||
}; |
|||
</script> |
|||
@ -0,0 +1,49 @@ |
|||
<template> |
|||
<div v-if="parlamentares && parlamentares.length > 0"> |
|||
<legend>Situação da Votação:</legend> |
|||
<div id="soma_votos"> |
|||
<div class="row"> |
|||
<div class="col-md-12">Sim: {{ votosSim }}</div> |
|||
</div> |
|||
<div class="row"> |
|||
<div class="col-md-12">Não: {{ votosNao }}</div> |
|||
</div> |
|||
<div class="row"> |
|||
<div class="col-md-12">Abstenções: {{ votosAbstencao }}</div> |
|||
</div> |
|||
<div class="row"> |
|||
<div class="col-md-12">Ainda não votaram: {{ naoVotou }}</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { mapGetters, mapState } from 'vuex'; |
|||
export default { |
|||
name: 'VotacaoResultado', |
|||
computed: { |
|||
...mapState(['parlamentares']), |
|||
...mapGetters(['totalVotos']), |
|||
votosSim() { |
|||
const entry = this.totalVotos.find(t => t.tipo === 'Sim'); |
|||
return entry ? entry.total : 0; |
|||
}, |
|||
votosNao() { |
|||
const entry = this.totalVotos.find(t => t.tipo === 'Não'); |
|||
return entry ? entry.total : 0; |
|||
}, |
|||
votosAbstencao() { |
|||
const entry = this.totalVotos.find(t => t.tipo === 'Abstenção'); |
|||
return entry ? entry.total : 0; |
|||
}, |
|||
naoVotou() { |
|||
const entry = this.totalVotos.find(t => t.tipo === 'Não Votou'); |
|||
return entry ? entry.total : 0; |
|||
} |
|||
}, |
|||
mounted() { |
|||
console.log('VotacaoResultado mounted'); |
|||
} |
|||
}; |
|||
</script> |
|||
@ -0,0 +1,44 @@ |
|||
<template> |
|||
<fieldset class="form-group" v-if="parlamentares && parlamentares.length > 0"> |
|||
<legend>Votos</legend> |
|||
<div class="row"> |
|||
<template v-for="p in parlamentares"> |
|||
<div class="col-md-4" id="styleparlamentar" :key="'nome_' + p.parlamentar_id"> |
|||
{{ p.nome_parlamentar }} |
|||
</div> |
|||
<div class="col-md-5" :key="'voto_' + p.parlamentar_id"> |
|||
<select class="form-control" |
|||
:name="'voto_parlamentar_' + p.parlamentar_id" |
|||
:value="p.voto || 'Não Votou'" |
|||
@change="onVoteChange(p.parlamentar_id, $event.target.value)"> |
|||
<option value="Não Votou">Não Votou</option> |
|||
<option value="Sim">Sim</option> |
|||
<option value="Não">Não</option> |
|||
<option value="Abstenção">Abstenção</option> |
|||
</select> |
|||
</div> |
|||
</template> |
|||
</div> |
|||
</fieldset> |
|||
<div class="alert alert-info alert-dismissible" role="alert" v-else> |
|||
<div>Não existe nenhum parlamentar presente para que a votação ocorra.</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { mapState } from 'vuex'; |
|||
export default { |
|||
name: 'VotacaoVotos', |
|||
computed: { |
|||
...mapState(['parlamentares']) |
|||
}, |
|||
methods: { |
|||
onVoteChange(parlamentar_id, voto) { |
|||
this.$emit('cast-vote', { parlamentar_id, voto }); |
|||
} |
|||
}, |
|||
mounted() { |
|||
console.log('VotacaoVotos mounted'); |
|||
} |
|||
}; |
|||
</script> |
|||
Loading…
Reference in new issue