Browse Source

feat(votacao): refatora template votacao_v2 para usar componentes Vue

pull/3840/head
RogerKoala 1 week ago
parent
commit
07c9d4c29e
  1. 569
      frontend/src/__apps/votacao/main.js
  2. 25
      frontend/src/components/votacao/VotacaoMateria.vue
  3. 76
      frontend/src/components/votacao/VotacaoNominal.vue
  4. 84
      frontend/src/components/votacao/VotacaoObservacoes.vue
  5. 49
      frontend/src/components/votacao/VotacaoResultado.vue
  6. 44
      frontend/src/components/votacao/VotacaoVotos.vue
  7. 65
      sapl/templates/sessao/votacao/votacao_v2.html

569
frontend/src/__apps/votacao/main.js

@ -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()
}
}
})

25
frontend/src/components/votacao/VotacaoMateria.vue

@ -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>

76
frontend/src/components/votacao/VotacaoNominal.vue

@ -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>

84
frontend/src/components/votacao/VotacaoObservacoes.vue

@ -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>

49
frontend/src/components/votacao/VotacaoResultado.vue

@ -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>

44
frontend/src/components/votacao/VotacaoVotos.vue

@ -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>

65
sapl/templates/sessao/votacao/votacao_v2.html

@ -7,62 +7,15 @@
{% block vue_content %}
<style>[v-cloak]{display:none}</style>
<div id="votacao">
<fieldset class="form-group">
<legend>[[ tipo_votacao_descricao ]]</legend>
[[ error_message ]]
<!-- Component 1 -->
<div>
<b>Matéria:</b> [[ materia ]]
<br/>
<b>Ementa:</b> [[ ementa ]]
<br/>
</div>
<br/>
<!-- Votacao Nominal -->
<fieldset class="form-group" v-if="tipo_votacao == 1">
<legend><strong>Votos</strong></legend>
<div class="row" v-for="p in parlamentares">
<div class="col-md-4" id="nome_parlamentar">[[ p.nome_parlamentar ]]</div>
<div class="col-md-2" id="filiacao">[[ p.filiacao ]]</div>
<div class="col-md-4" v-if="edit_votes">
<span v-for="o in options" :key="o.value">
<input type="radio" :id="o.value" :value="o.text" :name="p.parlamentar_id" v-model="votos_parlamentares[p.parlamentar_id].voto"/>
<label :for="o.value"> [[ o.text ]]</label>
&nbsp;
</span>
</div>
<div v-else>
[[ votos_parlamentares[p.parlamentar_id].voto ]]
</div>
</div>
<br/>
<br/>
</fieldset>
<legend><strong>Total de votos:</strong></legend>
<div id="total_votos" v-for="voto in total_votos">
<div class='row'><div class='col-md-12'>[[ voto.tipo ]]: [[ voto.total ]]</div></div>
</div>
<br/>
<!-- component -->
<div class="col-md-6">
<b>Resultado da Votação: </b>
<select v-model="resultado_selected" class="select form-control" :disabled="disable_resultado" >
<option :key="t.id" :value="t.id" v-for="t in tipos_resultados">[[ t.nome ]]</option>
</select>
</div>
<!-- component -->
<div class="row">
<div class="col-md-12">
Observações:
<textarea v-model="observacoes" cols="10" rows="10" class="form-control"></textarea>
</div>
</div>
<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>
</fieldset>
<div id="votacao" v-cloak data-controller-id="{{ pk }}">
<votacao-nominal
:is-open="isOpen"
:error-message="error_message"
@cast-vote="castVote"
@cancelar="onCancelar"
@fechar="onFechar"
ref="votacaoNominal">
</votacao-nominal>
</div>
{% endblock vue_content %}

Loading…
Cancel
Save