Browse Source

style(painel): corrige erros de ESLint nos cronômetros

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat/painel-votacao-v2
Edward Ribeiro 6 days ago
parent
commit
6e53b72e95
  1. 12
      frontend/src/__apps/painel/ws/normalizePainel.js
  2. 132
      frontend/src/components/painel/Cronometro.vue
  3. 244
      frontend/src/components/painel/CronometroList.vue

12
frontend/src/__apps/painel/ws/normalizePainel.js

@ -80,7 +80,7 @@ function normalizeCronometro (v) {
start_ts: null, start_ts: null,
remaining_at_start: 300, remaining_at_start: 300,
remaining_at_stop: null, remaining_at_stop: null,
updated_at: null, updated_at: null
} }
} }
if (typeof v === 'object') { if (typeof v === 'object') {
@ -89,11 +89,11 @@ function normalizeCronometro (v) {
const remaining_at_stop = v.remaining_at_stop != null ? v.remaining_at_stop : null const remaining_at_stop = v.remaining_at_stop != null ? v.remaining_at_stop : null
return { return {
action: v.action || '', action: v.action || '',
duration: duration, duration,
start_ts: v.start_ts != null ? v.start_ts : null, start_ts: v.start_ts != null ? v.start_ts : null,
remaining_at_start: remaining_at_start, remaining_at_start,
remaining_at_stop: remaining_at_stop, remaining_at_stop,
updated_at: v.updated_at != null ? v.updated_at : null, updated_at: v.updated_at != null ? v.updated_at : null
} }
} }
return null return null
@ -124,7 +124,7 @@ export function normalizePainelData (raw) {
nome_parlamentar: asString(o.nome, '') nome_parlamentar: asString(o.nome, '')
})) }))
console.log(d.sessao_iniciada); console.log(d.sessao_iniciada)
return { return {
// sessao_iniciada vem de sessao.iniciada is not False (True ou None // sessao_iniciada vem de sessao.iniciada is not False (True ou None

132
frontend/src/components/painel/Cronometro.vue

@ -12,33 +12,33 @@
<script> <script>
export default { export default {
name: 'Cronometro', name: 'PainelCronometro',
props: ['id', 'title', 'visible', 'colorClass'], props: ['id', 'title', 'visible', 'colorClass'],
data() { data () {
return { return {
time: 300, time: 300,
isRunning: false, isRunning: false,
initialTime: 300, initialTime: 300,
intervalId: null, intervalId: null,
audioSrc: require('@/assets/audio/ring.mp3'), audioSrc: require('@/assets/audio/ring.mp3')
} }
}, },
computed: { computed: {
titleColorClass() { titleColorClass () {
return this.colorClass || ''; return this.colorClass || ''
} }
}, },
mounted() { mounted () {
console.log('Cronometro mounted'); console.log('Cronometro mounted')
this.$emit('child-mounted'); this.$emit('child-mounted')
}, },
methods: { methods: {
changeFontSize(value) { changeFontSize (value) {
const el = this.$refs.time; const el = this.$refs.time
if (!el) return; if (!el) return
let fontSize = window.getComputedStyle(el).fontSize; let fontSize = window.getComputedStyle(el).fontSize
fontSize = parseFloat(fontSize); fontSize = parseFloat(fontSize)
el.style.fontSize = (fontSize + value) + 'px'; el.style.fontSize = (fontSize + value) + 'px'
}, },
/** /**
@ -53,114 +53,112 @@ export default {
* e inicia a contagem (idempotente: se já estiver rodando com * e inicia a contagem (idempotente: se já estiver rodando com
* o mesmo start_ts, não reinicia) * o mesmo start_ts, não reinicia)
*/ */
applyState(state) { applyState (state) {
if (!state || !state.action) { if (!state || !state.action) {
// Sem estado: apenas configura a duração e para // Sem estado: apenas configura a duração e para
this.stop(); this.stop()
this.time = this.initialTime; this.time = this.initialTime
return; return
} }
const { action, duration, start_ts, remaining_at_start, remaining_at_stop } = state; const { action, duration, start_ts, remaining_at_start, remaining_at_stop } = state
// Atualiza a duração configurada // Atualiza a duração configurada
if (duration != null) { if (duration != null) {
this.initialTime = duration; this.initialTime = duration
} }
if (action === 'reset') { if (action === 'reset') {
this.stop(); this.stop()
this.time = this.initialTime; this.time = this.initialTime
} else if (action === 'stop') { } else if (action === 'stop') {
this.stop(); this.stop()
// Mostra o tempo exato em que o operador parou o cronômetro // Mostra o tempo exato em que o operador parou o cronômetro
if (remaining_at_stop != null) { if (remaining_at_stop != null) {
this.time = Math.max(0, Math.round(remaining_at_stop)); this.time = Math.max(0, Math.round(remaining_at_stop))
} }
} else if (action === 'start') { } else if (action === 'start') {
// Calcula o tempo restante a partir de remaining_at_start e start_ts // Calcula o tempo restante a partir de remaining_at_start e start_ts
const nowSec = Date.now() / 1000; const nowSec = Date.now() / 1000
const elapsed = start_ts != null ? Math.max(0, nowSec - start_ts) : 0; const elapsed = start_ts != null ? Math.max(0, nowSec - start_ts) : 0
const baseRem = remaining_at_start != null const baseRem = remaining_at_start != null
? remaining_at_start ? remaining_at_start
: (remaining_at_stop != null : (remaining_at_stop != null
? remaining_at_stop ? remaining_at_stop
: (this.time > 0 && this.time < this.initialTime ? this.time : this.initialTime)); : (this.time > 0 && this.time < this.initialTime ? this.time : this.initialTime))
const remaining = Math.max(0, baseRem - elapsed); const remaining = Math.max(0, baseRem - elapsed)
// Idempotente: se já estiver rodando e a contagem estiver em sincronia // Idempotente: se já estiver rodando e a contagem estiver em sincronia
// (diferença <= 2s), mantém o intervalo nativo para não ter saltos // (diferença <= 2s), mantém o intervalo nativo para não ter saltos
if (!this.isRunning || Math.abs(this.time - remaining) > 2) { if (!this.isRunning || Math.abs(this.time - remaining) > 2) {
this.stop(); this.stop()
this.time = Math.round(remaining); this.time = Math.round(remaining)
if (remaining > 0) { if (remaining > 0) {
this._startInterval(); this._startInterval()
} }
} }
} }
this.$emit('state-changed', { id: this.id, isRunning: this.isRunning }); this.$emit('state-changed', { id: this.id, isRunning: this.isRunning })
}, },
_startInterval() { _startInterval () {
this.isRunning = true; this.isRunning = true
this.intervalId = setInterval(() => { this.intervalId = setInterval(() => {
if (this.time > 0) { if (this.time > 0) {
this.time--; this.time--
if (this.time == 30) { if (this.time === 30) {
this.playSound(); this.playSound()
} }
} else { } else {
this.isRunning = false; this.isRunning = false
clearInterval(this.intervalId); clearInterval(this.intervalId)
this.playSound(); this.playSound()
this.$emit('state-changed', { id: this.id, isRunning: false }); this.$emit('state-changed', { id: this.id, isRunning: false })
} }
}, 1000); }, 1000)
}, },
// start/stop/reset legados mantidos para compatibilidade com qualquer // start/stop/reset legados mantidos para compatibilidade com qualquer
// chamador externo, mas internamente agora delegam para applyState(). // chamador externo, mas internamente agora delegam para applyState().
start() { start () {
if (this.isRunning) return; if (this.isRunning) return
this._startInterval(); this._startInterval()
}, },
stop() { stop () {
this.isRunning = false; this.isRunning = false
clearInterval(this.intervalId); clearInterval(this.intervalId)
this.intervalId = null; this.intervalId = null
}, },
reset() { reset () {
this.stop(); this.stop()
this.time = this.initialTime; this.time = this.initialTime
}, },
playSound() { playSound () {
const audio = this.$refs.player const audio = this.$refs.player
if (!audio) return if (!audio) return
audio.play() audio.play()
}, },
formatTime(seconds) { formatTime (seconds) {
const hrs = Math.floor(seconds / 3600); const hrs = Math.floor(seconds / 3600)
const mins = Math.floor((seconds % 3600) / 60); const mins = Math.floor((seconds % 3600) / 60)
const secs = seconds % 60; const secs = seconds % 60
return `${hrs.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; return `${hrs.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
} }
}, },
watch: { watch: {
initialTime(newVal) { initialTime (newVal) {
if (!this.isRunning) { if (!this.isRunning) {
this.time = newVal; this.time = newVal
} }
} }
}, },
beforeDestroy() { beforeDestroy () {
clearInterval(this.intervalId); clearInterval(this.intervalId)
} }
} }
</script> </script>

244
frontend/src/components/painel/CronometroList.vue

@ -54,160 +54,152 @@
</template> </template>
<script> <script>
import { mapState } from 'pinia'; import { mapState } from 'pinia'
import { usePainelStore } from '@/__apps/painel/store/painelStore'; import { usePainelStore } from '@/__apps/painel/store/painelStore'
import Cronometro from './Cronometro.vue'; import Cronometro from './Cronometro.vue'
// Mapa: key do cronômetro → ref do componente filho export default {
const CRONOMETRO_REFS = [ name: 'CronometroList',
{ key: 'discurso', ref: 'childRef_0' }, components: {
{ key: 'aparte', ref: 'childRef_1' }, Cronometro
{ key: 'ordem', ref: 'childRef_2' }, },
{ key: 'consideracoes', ref: 'childRef_3' }, data () {
]; return {
// Qual cronômetro está visível no momento.
// Regra de prioridade ao rodar: ordem > aparte > consideracoes > discurso.
// Quando nenhum está rodando: mantém o último visível (para mostrar
// o tempo "parado" do cronômetro que acabou de ser parado).
visibleCronometro: 'discurso',
childrenMounted: 0,
allChildrenMounted: false
}
},
mounted () {
console.log('CronometroList mounted')
// Os filhos podem não ter montado ainda — aplica via nextTick após
// todos os filhos emitirem 'child-mounted' (ver handleChildMounted).
},
computed: {
...mapState(usePainelStore, [
'canRender',
'cronometro_ativo',
'cronometro_discurso', 'cronometro_aparte',
'cronometro_ordem', 'cronometro_consideracoes'
])
},
watch: {
// Quando o painel abre (canRender muda para true), os filhos
// acabam de montar — aplica o estado inicial de todos.
canRender (newVal) {
if (newVal) {
this.$nextTick(() => this.applyAllStates())
}
},
export default { cronometro_ativo (newVal) {
name: 'CronometroList', if (newVal) {
components: { this.updateVisibility()
Cronometro, }
}, },
data() {
return {
// Qual cronômetro está visível no momento.
// Regra de prioridade ao rodar: ordem > aparte > consideracoes > discurso.
// Quando nenhum está rodando: mantém o último visível (para mostrar
// o tempo "parado" do cronômetro que acabou de ser parado).
visibleCronometro: 'discurso',
childrenMounted: 0,
allChildrenMounted: false,
}
},
mounted() {
console.log('CronometroList mounted');
// Os filhos podem não ter montado ainda — aplica via nextTick após
// todos os filhos emitirem 'child-mounted' (ver handleChildMounted).
},
computed: {
...mapState(usePainelStore, [
"canRender",
"cronometro_ativo",
"cronometro_discurso", "cronometro_aparte",
"cronometro_ordem", "cronometro_consideracoes"
])
},
watch: {
// Quando o painel abre (canRender muda para true), os filhos
// acabam de montar — aplica o estado inicial de todos.
canRender(newVal) {
if (newVal) {
this.$nextTick(() => this.applyAllStates());
}
},
cronometro_ativo(newVal) { // Watchers profundos para cada cronômetro: disparam quando o objeto
if (newVal) { // mudar (nova action, duration, etc.) mas NÃO quando o broadcast
this.updateVisibility(); // chega com o mesmo valor (deep watch compara referência, mas o
} // store substitui o objeto inteiro — a comparação de ação é feita
}, // dentro de applyState do filho para ser idempotente).
cronometro_discurso (newVal) {
// Watchers profundos para cada cronômetro: disparam quando o objeto this.applyOneState('childRef_0', newVal)
// mudar (nova action, duration, etc.) mas NÃO quando o broadcast },
// chega com o mesmo valor (deep watch compara referência, mas o cronometro_aparte (newVal) {
// store substitui o objeto inteiro — a comparação de ação é feita this.applyOneState('childRef_1', newVal)
// dentro de applyState do filho para ser idempotente). },
cronometro_discurso(newVal) { cronometro_ordem (newVal) {
this.applyOneState('childRef_0', newVal); this.applyOneState('childRef_2', newVal)
}, },
cronometro_aparte(newVal) { cronometro_consideracoes (newVal) {
this.applyOneState('childRef_1', newVal); this.applyOneState('childRef_3', newVal)
}, }
cronometro_ordem(newVal) { },
this.applyOneState('childRef_2', newVal); methods: {
}, /**
cronometro_consideracoes(newVal) {
this.applyOneState('childRef_3', newVal);
},
},
methods: {
/**
* Aplica o estado de todos os cronômetros de uma vez. * Aplica o estado de todos os cronômetros de uma vez.
* Usado na montagem e quando o painel é aberto. * Usado na montagem e quando o painel é aberto.
*/ */
applyAllStates() { applyAllStates () {
this.applyOneState('childRef_0', this.cronometro_discurso); this.applyOneState('childRef_0', this.cronometro_discurso)
this.applyOneState('childRef_1', this.cronometro_aparte); this.applyOneState('childRef_1', this.cronometro_aparte)
this.applyOneState('childRef_2', this.cronometro_ordem); this.applyOneState('childRef_2', this.cronometro_ordem)
this.applyOneState('childRef_3', this.cronometro_consideracoes); this.applyOneState('childRef_3', this.cronometro_consideracoes)
this.updateVisibility(); this.updateVisibility()
}, },
/** /**
* Aplica o estado de um único cronômetro chamando applyState() no filho. * Aplica o estado de um único cronômetro chamando applyState() no filho.
* @param {string} refName - nome do ref ('childRef_0' etc.) * @param {string} refName - nome do ref ('childRef_0' etc.)
* @param {Object|null} state - { action, duration, start_ts?, remaining_at_stop? } * @param {Object|null} state - { action, duration, start_ts?, remaining_at_stop? }
*/ */
applyOneState(refName, state) { applyOneState (refName, state) {
const comp = this.$refs[refName]; const comp = this.$refs[refName]
if (!comp) return; if (!comp) return
comp.applyState(state); comp.applyState(state)
this.updateVisibility(); this.updateVisibility()
}, },
/** /**
* Chamado quando um filho monta. Quando todos os 4 filhos * Chamado quando um filho monta. Quando todos os 4 filhos
* montaram, aplica o estado inicial. * montaram, aplica o estado inicial.
*/ */
handleChildMounted() { handleChildMounted () {
this.childrenMounted++; this.childrenMounted++
console.log(`Cronometro child mounted (${this.childrenMounted}/4)`); console.log(`Cronometro child mounted (${this.childrenMounted}/4)`)
if (this.childrenMounted >= 4) { if (this.childrenMounted >= 4) {
this.allChildrenMounted = true; this.allChildrenMounted = true
this.applyAllStates(); this.applyAllStates()
} }
}, },
/** /**
* Chamado quando um filho muda de estado (start/stop). * Chamado quando um filho muda de estado (start/stop).
* Atualiza a visibilidade. * Atualiza a visibilidade.
*/ */
handleStateChanged() { handleStateChanged () {
this.updateVisibility(); this.updateVisibility()
}, },
/** /**
* Decide qual cronômetro mostrar. * Decide qual cronômetro mostrar.
* *
* Prioridade ao rodar: ordem > aparte > consideracoes > discurso. * Prioridade ao rodar: ordem > aparte > consideracoes > discurso.
* Se nenhum estiver rodando, mantém o último visível para exibir * Se nenhum estiver rodando, mantém o último visível para exibir
* o tempo no momento em que foi parado (comportamento igual ao discurso). * o tempo no momento em que foi parado (comportamento igual ao discurso).
*/ */
updateVisibility() { updateVisibility () {
// Prioridade 1: se algum cronômetro estiver rodando, mostra o que está rodando // Prioridade 1: se algum cronômetro estiver rodando, mostra o que está rodando
const priority = [ const priority = [
{ key: 'ordem', ref: 'childRef_2' }, { key: 'ordem', ref: 'childRef_2' },
{ key: 'aparte', ref: 'childRef_1' }, { key: 'aparte', ref: 'childRef_1' },
{ key: 'consideracoes', ref: 'childRef_3' }, { key: 'consideracoes', ref: 'childRef_3' },
{ key: 'discurso', ref: 'childRef_0' }, { key: 'discurso', ref: 'childRef_0' }
]; ]
for (const item of priority) { for (const item of priority) {
const comp = this.$refs[item.ref]; const comp = this.$refs[item.ref]
if (comp && comp.isRunning) { if (comp && comp.isRunning) {
this.visibleCronometro = item.key; this.visibleCronometro = item.key
return; return
} }
} }
// Prioridade 2: nenhum rodando. Mostra o último cronômetro operado pelo operador // Prioridade 2: nenhum rodando. Mostra o último cronômetro operado pelo operador
if (this.cronometro_ativo) { if (this.cronometro_ativo) {
this.visibleCronometro = this.cronometro_ativo; this.visibleCronometro = this.cronometro_ativo
return; return
} }
// Fallback padrão // Fallback padrão
this.visibleCronometro = 'discurso'; this.visibleCronometro = 'discurso'
} }
}, }
}; }
</script> </script>
<style scoped> <style scoped>

Loading…
Cancel
Save