Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 89 additions & 1 deletion frontend/src/components/Vote/VoteRating.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@
</span>
</div>

<div v-if="round && round.show_stats && voteStats" class="vote-statistics">
<h3 class="vote-section-title">{{ $t('montage-round-stats') }}</h3>
<div class="vote-stats-content">
<div v-for="star in [1, 2, 3, 4, 5]" :key="star" class="vote-stats-item">
<span class="vote-stats-label">
<star class="vote-stats-star-icon" />
{{ star }}:
</span>
<span class="vote-stats-value">{{ voteStats.stats?.[star] || 0 }}</span>
</div>
</div>
</div>

<h3 class="vote-section-title">{{ $t('montage-vote-actions') }}</h3>
<div class="vote-actions">
<div>
Expand Down Expand Up @@ -163,7 +176,7 @@
</template>

<script setup>
import { ref, watch, computed } from 'vue'
import { ref, watch, computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import jurorService from '@/services/jurorService'
import { useRouter } from 'vue-router'
Expand Down Expand Up @@ -207,6 +220,7 @@ const roundLink = [props.round.id, props.round.canonical_url_name].join('-')

const images = ref(null)
const stats = ref(null)
const voteStats = ref(null)

const rating = ref({
current: null,
Expand All @@ -223,6 +237,20 @@ function goPrevVoteEditing() {
router.push({ name: 'vote-edit', params: { id: roundLink } })
}

function fetchVoteStats() {
if (props.round?.show_stats) {
jurorService.getRoundVotesStats(props.round.id)
.then((response) => {
// API interceptor already returns response.data, so response is the data itself
voteStats.value = response
})
.catch(() => {
// Silently fail if stats can't be loaded
voteStats.value = null
})
}
}

function handleImageLoad() {
imageLoading.value = false
}
Expand Down Expand Up @@ -276,6 +304,10 @@ function setRate(rate) {
if (stats.value.total_open_tasks <= 10) {
skips.value = 0
}
// Refresh statistics after voting
if (props.round?.show_stats) {
fetchVoteStats()
}
if (counter.value === 4 || !stats.value.total_open_tasks) {
counter.value = 0
getTasks()
Expand Down Expand Up @@ -415,6 +447,24 @@ watch( voteContainer, () => {
voteContainer.value.focus();
}
});

watch(
() => props.round,
(round) => {
if (round?.show_stats) {
fetchVoteStats()
} else {
voteStats.value = null
}
},
{ immediate: true }
)

onMounted(() => {
if (props.round?.show_stats) {
fetchVoteStats()
}
})
</script>

<style scoped>
Expand Down Expand Up @@ -603,4 +653,42 @@ watch( voteContainer, () => {
margin-top: 24px;
width: 232px;
}

.vote-statistics {
margin-top: 16px;
}

.vote-stats-content {
display: flex;
flex-direction: column;
gap: 8px;
}

.vote-stats-item {
display: flex;
justify-content: space-between;
align-items: center;
}

.vote-stats-label {
font-size: 14px;
color: rgba(0, 0, 0, 0.87);
display: flex;
align-items: center;
gap: 4px;
}

.vote-stats-star-icon {
width: 16px;
height: 16px;
display: inline-flex;
align-items: center;
justify-content: center;
}

.vote-stats-value {
font-size: 16px;
font-weight: 500;
color: rgba(0, 0, 0, 0.87);
}
</style>
80 changes: 79 additions & 1 deletion frontend/src/components/Vote/VoteYesNo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,20 @@
</span>
</div>

<div v-if="round && round.show_stats && voteStats" class="vote-statistics">
<h3 class="vote-section-title">{{ $t('montage-round-stats') }}</h3>
<div class="vote-stats-content">
<div class="vote-stats-item">
<span class="vote-stats-label">{{ $t('montage-vote-accept') }}:</span>
<span class="vote-stats-value">{{ voteStats.stats?.yes || 0 }}</span>
</div>
<div class="vote-stats-item">
<span class="vote-stats-label">{{ $t('montage-vote-decline') }}:</span>
<span class="vote-stats-value">{{ voteStats.stats?.no || 0 }}</span>
</div>
</div>
</div>

<h3 class="vote-section-title">{{ $t('montage-vote-actions') }}</h3>
<div class="vote-actions">
<div>
Expand Down Expand Up @@ -164,7 +178,7 @@
</template>

<script setup>
import { ref, watch, computed } from 'vue'
import { ref, watch, computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import jurorService from '@/services/jurorService'
import { useRouter } from 'vue-router'
Expand Down Expand Up @@ -209,6 +223,7 @@ const roundLink = [props.round.id, props.round.canonical_url_name].join('-')
const isLoading = ref(false)
const images = ref(null)
const stats = ref(null)
const voteStats = ref(null)

const rating = ref({
current: null,
Expand All @@ -225,6 +240,20 @@ const goPrevVoteEditing = () => {
router.push({ name: 'vote-edit', params: { id: roundLink } })
}

const fetchVoteStats = () => {
if (props.round?.show_stats) {
jurorService.getRoundVotesStats(props.round.id)
.then((response) => {
// API interceptor already returns response.data, so response is the data itself
voteStats.value = response
})
.catch(() => {
// Silently fail if stats can't be loaded
voteStats.value = null
})
}
}

const handleImageLoad = () => {
imageLoading.value = false
}
Expand Down Expand Up @@ -278,6 +307,10 @@ function setRate(rate) {
if (stats.value.total_open_tasks <= 10) {
skips.value = 0
}
// Refresh statistics after voting
if (props.round?.show_stats) {
fetchVoteStats()
}
if (counter.value === 4 || !stats.value.total_open_tasks) {
counter.value = 0
getTasks()
Expand Down Expand Up @@ -419,6 +452,24 @@ watch( voteContainer, () => {
voteContainer.value.focus();
}
});

watch(
() => props.round,
(round) => {
if (round?.show_stats) {
fetchVoteStats()
} else {
voteStats.value = null
}
},
{ immediate: true }
)

onMounted(() => {
if (props.round?.show_stats) {
fetchVoteStats()
}
})
</script>

<style scoped>
Expand Down Expand Up @@ -607,4 +658,31 @@ watch( voteContainer, () => {
margin-top: 24px;
width: 232px;
}

.vote-statistics {
margin-top: 16px;
}

.vote-stats-content {
display: flex;
flex-direction: column;
gap: 8px;
}

.vote-stats-item {
display: flex;
justify-content: space-between;
align-items: center;
}

.vote-stats-label {
font-size: 14px;
color: rgba(0, 0, 0, 0.87);
}

.vote-stats-value {
font-size: 16px;
font-weight: 500;
color: rgba(0, 0, 0, 0.87);
}
</style>
4 changes: 4 additions & 0 deletions montage/juror_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ def get_votes_stats_from_round(user_dao, round_id):
juror_dao = JurorDAO(user_dao)
rnd = juror_dao.get_round(round_id)

# Only return statistics if show_stats is enabled
if not rnd.show_stats:
return None

stats = None

if rnd.vote_method == 'yesno':
Expand Down