From 58159119cfca95a37f8082fdaf5f7ca460e16d65 Mon Sep 17 00:00:00 2001 From: Frank Tovar Date: Wed, 4 Feb 2026 23:02:09 +0100 Subject: [PATCH] Add vote finalize UX and missing-vote warning placement --- wwwroot/index.html | 2 ++ wwwroot/js/data.js | 1 + wwwroot/js/i18n.js | 6 ++++++ wwwroot/js/state.js | 2 ++ wwwroot/js/ui.js | 20 ++++++++++++++++++++ 5 files changed, 31 insertions(+) diff --git a/wwwroot/index.html b/wwwroot/index.html index 700435f..7b260f8 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -118,10 +118,12 @@ diff --git a/wwwroot/js/data.js b/wwwroot/js/data.js index 1d7e926..495dcdc 100644 --- a/wwwroot/js/data.js +++ b/wwwroot/js/data.js @@ -9,6 +9,7 @@ export async function loadState() { state.prevPhase = state.phase; state.phase = stateData.currentPhase; state.resultsOpen = stateData.resultsOpen; + state.votesFinal = stateData.votesFinal ?? me?.votesFinal ?? false; state.counts = stateData; if (state.prevPhase !== state.phase && state.phase === "Vote") { state.votesRendered = false; diff --git a/wwwroot/js/i18n.js b/wwwroot/js/i18n.js index 037cf3e..6b6a9e7 100644 --- a/wwwroot/js/i18n.js +++ b/wwwroot/js/i18n.js @@ -78,6 +78,9 @@ const translations = { "vote.saved": "Saved vote", "vote.missing": "Missing", "vote.missingWarn": "You haven’t voted yet. Slide to set a score.", + "vote.finalize": "Finalize votes", + "vote.unfinalize": "Edit votes", + "vote.finalHint": "Finalize when you’re done. You can unfinalize to change scores.", "results.rank": "Rank", "results.game": "Game", @@ -197,6 +200,9 @@ const translations = { "vote.saved": "Stimme gespeichert", "vote.missing": "Fehlt", "vote.missingWarn": "Du hast hier noch nicht abgestimmt. Schiebe den Regler.", + "vote.finalize": "Abstimmung abschließen", + "vote.unfinalize": "Abstimmung bearbeiten", + "vote.finalHint": "Schließe ab, wenn du fertig bist. Zum Ändern wieder öffnen.", "results.rank": "Rang", "results.game": "Spiel", diff --git a/wwwroot/js/state.js b/wwwroot/js/state.js index 34ee56f..fd8cac2 100644 --- a/wwwroot/js/state.js +++ b/wwwroot/js/state.js @@ -5,6 +5,7 @@ export const state = { phase: null, prevPhase: null, resultsOpen: false, + votesFinal: false, counts: null, mySuggestions: [], allSuggestions: [], @@ -19,6 +20,7 @@ export function clearUserState() { state.phase = null; state.prevPhase = null; state.resultsOpen = false; + state.votesFinal = false; state.counts = null; state.mySuggestions = []; state.allSuggestions = []; diff --git a/wwwroot/js/ui.js b/wwwroot/js/ui.js index b00e4f3..08a1710 100644 --- a/wwwroot/js/ui.js +++ b/wwwroot/js/ui.js @@ -642,6 +642,14 @@ function formatMyVote(score) { return `${score} ${scoreToEmoji(score)}`; } +function missingVotesCount() { + const total = state.allSuggestions?.length ?? 0; + const mine = state.myVotes?.length ?? 0; + const votedIds = new Set(state.myVotes?.map((v) => v.suggestionId)); + const missing = total - votedIds.size; + return missing < 0 ? 0 : missing; +} + function openDeleteConfirmModal(s) { const overlay = document.createElement("div"); overlay.className = "edit-modal"; @@ -733,6 +741,18 @@ export function updatePhaseNav() { lockBadge.textContent = t("admin.resultsLocked"); } + const finalizeBtn = $("finalize-votes"); + if (finalizeBtn) { + finalizeBtn.textContent = state.votesFinal ? t("vote.unfinalize") : t("vote.finalize"); + } + + const voteMissingBadge = $("vote-missing"); + if (voteMissingBadge) { + const missing = missingVotesCount(); + voteMissingBadge.classList.toggle("hidden", missing === 0); + voteMissingBadge.textContent = missing > 0 ? t("vote.missingWarn") : ""; + } + // Toggle admin-only back buttons const backButtons = ["nav-vote-prev"]; backButtons.forEach((id) => {