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,
remaining_at_start: 300,
remaining_at_stop: null,
updated_at: null,
updated_at: null
}
}
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
return {
action: v.action || '',
duration: duration,
duration,
start_ts: v.start_ts != null ? v.start_ts : null,
remaining_at_start: remaining_at_start,
remaining_at_stop: remaining_at_stop,
updated_at: v.updated_at != null ? v.updated_at : null,
remaining_at_start,
remaining_at_stop,
updated_at: v.updated_at != null ? v.updated_at : null
}
}
return null
@ -124,7 +124,7 @@ export function normalizePainelData (raw) {
nome_parlamentar: asString(o.nome, '')
}))
console.log(d.sessao_iniciada);
console.log(d.sessao_iniciada)
return {
// sessao_iniciada vem de sessao.iniciada is not False (True ou None

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

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

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

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

Loading…
Cancel
Save