diff --git a/platforms/eVoting/src/app/(app)/[id]/page.tsx b/platforms/eVoting/src/app/(app)/[id]/page.tsx index 0219be44..920a7bb1 100644 --- a/platforms/eVoting/src/app/(app)/[id]/page.tsx +++ b/platforms/eVoting/src/app/(app)/[id]/page.tsx @@ -421,12 +421,12 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) { {result.optionText || `Option ${index + 1}`} - {result.isTied && ( + {result.isTied && blindVoteResults.totalVotes > 0 && ( 🏆 Tied )} - {isWinner && !result.isTied && ( + {isWinner && !result.isTied && blindVoteResults.totalVotes > 0 && ( 🏆 Winner @@ -480,6 +480,9 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) {
{resultsData.totalVotes || 0} of {resultsData.totalEligibleVoters} eligible voters + {(resultsData.mode === "ereputation" || selectedPoll?.votingWeight === "ereputation") && resultsData.pointsVoted !== undefined && resultsData.totalEligiblePoints !== undefined && ( + <> ({resultsData.pointsVoted} points of {resultsData.totalEligiblePoints} total eligible points) + )}
@@ -521,7 +524,8 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) { } else { // Normal voting (including eReputation weighted normal voting): show votes and percentage const voteCount = result.votes || 0; - displayValue = `${voteCount} votes`; + // For eReputation mode, show "Points" instead of "votes" + displayValue = resultsData.mode === "ereputation" ? `${voteCount} Points` : `${voteCount} votes`; // Calculate total from results array for percentage (handles both weighted and non-weighted) const totalVotesForPercentage = resultsData.results.reduce((sum, r) => sum + (r.votes || 0), 0); isWinner = voteCount === Math.max(...resultsData.results.map(r => r.votes || 0)); @@ -541,12 +545,12 @@ export default function Vote({ params }: { params: Promise<{ id: string }> }) { {result.option} - {result.isTied && ( + {result.isTied && resultsData.totalVotes > 0 && ( 🏆 Tied )} - {isWinner && !result.isTied && ( + {isWinner && !result.isTied && resultsData.totalVotes > 0 && ( 🏆 Winner diff --git a/platforms/eVoting/src/app/(app)/page.tsx b/platforms/eVoting/src/app/(app)/page.tsx index fae0b3cd..eecccce3 100644 --- a/platforms/eVoting/src/app/(app)/page.tsx +++ b/platforms/eVoting/src/app/(app)/page.tsx @@ -17,8 +17,18 @@ export default function Home() { const [currentPage, setCurrentPage] = useState(1); const [searchTerm, setSearchTerm] = useState(""); const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(""); - const [sortField, setSortField] = useState("deadline"); - const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc"); + const [sortField, setSortField] = useState(() => { + if (typeof window !== "undefined") { + return localStorage.getItem("evoting_sortField") || "deadline"; + } + return "deadline"; + }); + const [sortDirection, setSortDirection] = useState<"asc" | "desc">(() => { + if (typeof window !== "undefined") { + return (localStorage.getItem("evoting_sortDirection") as "asc" | "desc") || "asc"; + } + return "asc"; + }); const itemsPerPage = 15; useEffect(() => { @@ -75,10 +85,18 @@ export default function Home() { const handleSort = (field: string) => { if (sortField === field) { - setSortDirection(sortDirection === "asc" ? "desc" : "asc"); + const newDirection = sortDirection === "asc" ? "desc" : "asc"; + setSortDirection(newDirection); + if (typeof window !== "undefined") { + localStorage.setItem("evoting_sortDirection", newDirection); + } } else { setSortField(field); setSortDirection("asc"); + if (typeof window !== "undefined") { + localStorage.setItem("evoting_sortField", field); + localStorage.setItem("evoting_sortDirection", "asc"); + } } }; diff --git a/platforms/eVoting/src/lib/pollApi.ts b/platforms/eVoting/src/lib/pollApi.ts index c0dadbb9..a0b33014 100644 --- a/platforms/eVoting/src/lib/pollApi.ts +++ b/platforms/eVoting/src/lib/pollApi.ts @@ -121,6 +121,8 @@ export interface PollResults { results: PollResultOption[]; irvDetails?: IRVDetails; voterDetails?: VoterDetail[]; + pointsVoted?: number; + totalEligiblePoints?: number; } export interface BlindVoteOptionResult { diff --git a/platforms/evoting-api/src/services/PollService.ts b/platforms/evoting-api/src/services/PollService.ts index 8b61fe15..1d9d3829 100644 --- a/platforms/evoting-api/src/services/PollService.ts +++ b/platforms/evoting-api/src/services/PollService.ts @@ -89,13 +89,7 @@ export class PollService { const aIsActive = !a.deadline || new Date(a.deadline) > now; const bIsActive = !b.deadline || new Date(b.deadline) > now; - // ALWAYS show active polls first, UNLESS sorting by status - if (sortField !== "status") { - if (aIsActive && !bIsActive) return -1; - if (!aIsActive && bIsActive) return 1; - } - - // If both are active or both are ended, apply the user's chosen sorting + // Apply the user's chosen sorting let comparison = 0; switch (sortField) { diff --git a/platforms/evoting-api/src/services/VoteService.ts b/platforms/evoting-api/src/services/VoteService.ts index 8b2fe225..afbe59e4 100644 --- a/platforms/evoting-api/src/services/VoteService.ts +++ b/platforms/evoting-api/src/services/VoteService.ts @@ -275,6 +275,15 @@ export class VoteService { ? Object.values(finalResults).reduce((sum, r) => sum + r.votes, 0) : Object.values(optionCounts).reduce((sum, count) => sum + count, 0); + // Calculate totalEligiblePoints and pointsVoted for eReputation + let totalEligiblePoints: number | undefined; + let pointsVoted: number | undefined; + if (isWeighted && reputationResults) { + // eReputation weighted normal voting: sum of all scores + totalEligiblePoints = reputationResults.results.reduce((sum, r) => sum + r.score, 0); + pointsVoted = totalWeightedVotes; // Sum of weighted votes + } + return { pollId, totalVotes: votes.length, // Actual number of votes cast @@ -282,7 +291,9 @@ export class VoteService { totalEligibleVoters, turnout: totalEligibleVoters > 0 ? (votes.length / totalEligibleVoters) * 100 : 0, mode: isWeighted ? "ereputation" : "normal", - results: finalResults + results: finalResults, + ...(totalEligiblePoints !== undefined && { totalEligiblePoints }), + ...(pointsVoted !== undefined && { pointsVoted }) }; } else if (poll.mode === "point") { // STEP 1: Calculate point-based results normally (without eReputation weighting) @@ -341,6 +352,15 @@ export class VoteService { // Sort by total points (highest first) finalResults.sort((a, b) => b.totalPoints - a.totalPoints); + // Calculate totalEligiblePoints and pointsVoted for eReputation weighted points-based voting + let totalEligiblePoints: number | undefined; + let pointsVoted: number | undefined; + if (reputationResults) { + // eReputation weighted points-based voting: sum of all scores * 100 (each user has 100 points) + totalEligiblePoints = reputationResults.results.reduce((sum, r) => sum + r.score, 0) * 100; + pointsVoted = totalWeightedPoints; // Sum of weighted points + } + return { pollId, totalVotes, @@ -348,7 +368,9 @@ export class VoteService { totalEligibleVoters, turnout: totalEligibleVoters > 0 ? (totalVotes / totalEligibleVoters) * 100 : 0, mode: "ereputation", - results: finalResults + results: finalResults, + ...(totalEligiblePoints !== undefined && { totalEligiblePoints }), + ...(pointsVoted !== undefined && { pointsVoted }) }; } else { // No weighting - use normal points @@ -369,6 +391,11 @@ export class VoteService { // Sort by total points (highest first) finalResults.sort((a, b) => b.totalPoints - a.totalPoints); + // Calculate totalEligiblePoints for simple points-based voting (not eReputation) + // Each user has 100 points by default + const totalEligiblePoints = totalEligibleVoters * 100; + const pointsVoted = totalPoints; // Sum of all points distributed + return { pollId, totalVotes, @@ -376,7 +403,9 @@ export class VoteService { totalEligibleVoters, turnout: totalEligibleVoters > 0 ? (totalVotes / totalEligibleVoters) * 100 : 0, mode: "point", - results: finalResults + results: finalResults, + totalEligiblePoints, + pointsVoted }; } } else if (poll.mode === "rank") {